Initial one-time snapshot of frappe-bench
This commit is contained in:
commit
3c5851d803
23771 changed files with 5817745 additions and 0 deletions
18
Procfile
Normal file
18
Procfile
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
|
||||||
|
redis_cache: redis-server config/redis_cache.conf
|
||||||
|
redis_queue: redis-server config/redis_queue.conf
|
||||||
|
|
||||||
|
|
||||||
|
web: bench serve --port 8000
|
||||||
|
|
||||||
|
|
||||||
|
socketio: /home/pilot/.nvm/versions/node/v18.20.8/bin/node apps/frappe/socketio.js
|
||||||
|
|
||||||
|
|
||||||
|
watch: bench watch
|
||||||
|
|
||||||
|
|
||||||
|
schedule: bench schedule
|
||||||
|
|
||||||
|
worker: bench worker 1>> logs/worker.log 2>> logs/worker.error.log
|
||||||
|
|
||||||
1
apps/check_run
Submodule
1
apps/check_run
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 5ef6e3f7031901bcf8aa82548fc14071e3633c6d
|
||||||
1
apps/erpnext
Submodule
1
apps/erpnext
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 31fa1c9a581c62fa5b1338725a3a334a709d163b
|
||||||
1
apps/frappe
Submodule
1
apps/frappe
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit c073721f116fad219a2dd302d4a9ba2ae6e64673
|
||||||
1
apps/hrms
Submodule
1
apps/hrms
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 3e02e2c9c9e90e092be3ecbae363faf58af39012
|
||||||
1
apps/suppliers_portal
Submodule
1
apps/suppliers_portal
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 7f2e860738547271daaf6ce6dc2ebe8e1d2dacad
|
||||||
1
apps/wiki
Submodule
1
apps/wiki
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit af7de232b6a7e25e6e8984abcd5c4f985a17587e
|
||||||
0
archived/sites/accounting.seahaven.com/.test_log
Normal file
0
archived/sites/accounting.seahaven.com/.test_log
Normal file
134
archived/sites/accounting.seahaven.com/logs/database.log
Normal file
134
archived/sites/accounting.seahaven.com/logs/database.log
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
2025-05-20 13:22:17,985 WARNING database DDL Query made to DB:
|
||||||
|
create table `tabMilestone` (
|
||||||
|
name varchar(140) primary key,
|
||||||
|
creation datetime(6),
|
||||||
|
modified datetime(6),
|
||||||
|
modified_by varchar(140),
|
||||||
|
owner varchar(140),
|
||||||
|
docstatus int(1) not null default '0',
|
||||||
|
idx int(8) not null default '0',
|
||||||
|
`reference_type` varchar(140),
|
||||||
|
`reference_name` varchar(140),
|
||||||
|
`track_field` varchar(140),
|
||||||
|
`value` varchar(140),
|
||||||
|
`milestone_tracker` varchar(140),
|
||||||
|
`_user_tags` text,
|
||||||
|
`_comments` text,
|
||||||
|
`_assign` text,
|
||||||
|
`_liked_by` text,
|
||||||
|
index `reference_type`(`reference_type`),
|
||||||
|
index modified(modified))
|
||||||
|
ENGINE=InnoDB
|
||||||
|
ROW_FORMAT=DYNAMIC
|
||||||
|
CHARACTER SET=utf8mb4
|
||||||
|
COLLATE=utf8mb4_unicode_ci
|
||||||
|
2025-05-20 13:22:18,027 WARNING database DDL Query made to DB:
|
||||||
|
ALTER TABLE `tabMilestone`
|
||||||
|
ADD INDEX IF NOT EXISTS `reference_type_reference_name_index`(reference_type, reference_name)
|
||||||
|
2025-05-20 13:22:18,072 WARNING database DDL Query made to DB:
|
||||||
|
create table `tabAssignment Rule Day` (
|
||||||
|
name varchar(140) primary key,
|
||||||
|
creation datetime(6),
|
||||||
|
modified datetime(6),
|
||||||
|
modified_by varchar(140),
|
||||||
|
owner varchar(140),
|
||||||
|
docstatus int(1) not null default '0',
|
||||||
|
idx int(8) not null default '0',
|
||||||
|
`day` varchar(140),
|
||||||
|
parent varchar(140),
|
||||||
|
parentfield varchar(140),
|
||||||
|
parenttype varchar(140),
|
||||||
|
index parent(parent))
|
||||||
|
ENGINE=InnoDB
|
||||||
|
ROW_FORMAT=DYNAMIC
|
||||||
|
CHARACTER SET=utf8mb4
|
||||||
|
COLLATE=utf8mb4_unicode_ci
|
||||||
|
2025-05-20 13:22:18,161 WARNING database DDL Query made to DB:
|
||||||
|
create table `tabAuto Repeat` (
|
||||||
|
name varchar(140) primary key,
|
||||||
|
creation datetime(6),
|
||||||
|
modified datetime(6),
|
||||||
|
modified_by varchar(140),
|
||||||
|
owner varchar(140),
|
||||||
|
docstatus int(1) not null default '0',
|
||||||
|
idx int(8) not null default '0',
|
||||||
|
`reference_doctype` varchar(140),
|
||||||
|
`reference_document` varchar(140),
|
||||||
|
`submit_on_creation` int(1) not null default 0,
|
||||||
|
`start_date` date,
|
||||||
|
`end_date` date,
|
||||||
|
`disabled` int(1) not null default 0,
|
||||||
|
`frequency` varchar(140),
|
||||||
|
`repeat_on_day` int(11) not null default 0,
|
||||||
|
`repeat_on_last_day` int(1) not null default 0,
|
||||||
|
`next_schedule_date` date,
|
||||||
|
`notify_by_email` int(1) not null default 0,
|
||||||
|
`recipients` text,
|
||||||
|
`template` varchar(140),
|
||||||
|
`subject` varchar(140),
|
||||||
|
`message` text default 'Please find attached {{ doc.doctype }} #{{ doc.name }}',
|
||||||
|
`print_format` varchar(140),
|
||||||
|
`status` varchar(140),
|
||||||
|
`_user_tags` text,
|
||||||
|
`_comments` text,
|
||||||
|
`_assign` text,
|
||||||
|
`_liked_by` text,
|
||||||
|
index `next_schedule_date`(`next_schedule_date`),
|
||||||
|
index modified(modified))
|
||||||
|
ENGINE=InnoDB
|
||||||
|
ROW_FORMAT=DYNAMIC
|
||||||
|
CHARACTER SET=utf8mb4
|
||||||
|
COLLATE=utf8mb4_unicode_ci
|
||||||
|
2025-05-20 13:22:18,244 WARNING database DDL Query made to DB:
|
||||||
|
create table `tabAssignment Rule` (
|
||||||
|
name varchar(140) primary key,
|
||||||
|
creation datetime(6),
|
||||||
|
modified datetime(6),
|
||||||
|
modified_by varchar(140),
|
||||||
|
owner varchar(140),
|
||||||
|
docstatus int(1) not null default '0',
|
||||||
|
idx int(8) not null default '0',
|
||||||
|
`document_type` varchar(140),
|
||||||
|
`due_date_based_on` varchar(140),
|
||||||
|
`priority` int(11) not null default 0,
|
||||||
|
`disabled` int(1) not null default 0,
|
||||||
|
`description` text default 'Automatic Assignment',
|
||||||
|
`assign_condition` longtext,
|
||||||
|
`unassign_condition` longtext,
|
||||||
|
`close_condition` longtext,
|
||||||
|
`rule` varchar(140),
|
||||||
|
`field` varchar(140),
|
||||||
|
`last_user` varchar(140),
|
||||||
|
`_user_tags` text,
|
||||||
|
`_comments` text,
|
||||||
|
`_assign` text,
|
||||||
|
`_liked_by` text,
|
||||||
|
index modified(modified))
|
||||||
|
ENGINE=InnoDB
|
||||||
|
ROW_FORMAT=DYNAMIC
|
||||||
|
CHARACTER SET=utf8mb4
|
||||||
|
COLLATE=utf8mb4_unicode_ci
|
||||||
|
2025-05-20 13:22:18,300 WARNING database DDL Query made to DB:
|
||||||
|
create table `tabAuto Repeat Day` (
|
||||||
|
name varchar(140) primary key,
|
||||||
|
creation datetime(6),
|
||||||
|
modified datetime(6),
|
||||||
|
modified_by varchar(140),
|
||||||
|
owner varchar(140),
|
||||||
|
docstatus int(1) not null default '0',
|
||||||
|
idx int(8) not null default '0',
|
||||||
|
`day` varchar(140),
|
||||||
|
parent varchar(140),
|
||||||
|
parentfield varchar(140),
|
||||||
|
parenttype varchar(140),
|
||||||
|
index parent(parent))
|
||||||
|
ENGINE=InnoDB
|
||||||
|
ROW_FORMAT=DYNAMIC
|
||||||
|
CHARACTER SET=utf8mb4
|
||||||
|
COLLATE=utf8mb4_unicode_ci
|
||||||
|
2025-05-20 13:27:11,019 WARNING database DDL Query made to DB:
|
||||||
|
DROP DATABASE IF EXISTS `_06925c1bee92171f`
|
||||||
|
2025-05-20 13:27:11,020 WARNING database DDL Query made to DB:
|
||||||
|
DROP USER IF EXISTS '_06925c1bee92171f'@'%'
|
||||||
|
2025-05-20 13:27:11,021 WARNING database DDL Query made to DB:
|
||||||
|
DROP USER IF EXISTS '_06925c1bee92171f'@'localhost'
|
||||||
3338
archived/sites/accounting.seahaven.com/logs/database.log.1
Normal file
3338
archived/sites/accounting.seahaven.com/logs/database.log.1
Normal file
File diff suppressed because it is too large
Load diff
2982
archived/sites/accounting.seahaven.com/logs/database.log.2
Normal file
2982
archived/sites/accounting.seahaven.com/logs/database.log.2
Normal file
File diff suppressed because it is too large
Load diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"db_name": "_06925c1bee92171f",
|
||||||
|
"db_password": "gCnAagZW9RvW1Foh",
|
||||||
|
"db_type": "mariadb"
|
||||||
|
}
|
||||||
5
archived/sites/accounting.seahaven.com/site_config.json
Normal file
5
archived/sites/accounting.seahaven.com/site_config.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"db_name": "_06925c1bee92171f",
|
||||||
|
"db_password": "gCnAagZW9RvW1Foh",
|
||||||
|
"db_type": "mariadb"
|
||||||
|
}
|
||||||
0
archived/sites/erp.seahaven.com/.test_log
Normal file
0
archived/sites/erp.seahaven.com/.test_log
Normal file
0
archived/sites/erp.seahaven.com/locks/install_app.lock
Normal file
0
archived/sites/erp.seahaven.com/locks/install_app.lock
Normal file
0
archived/sites/erp.seahaven.com/locks/site_config.lock
Normal file
0
archived/sites/erp.seahaven.com/locks/site_config.lock
Normal file
1983
archived/sites/erp.seahaven.com/logs/database.log
Normal file
1983
archived/sites/erp.seahaven.com/logs/database.log
Normal file
File diff suppressed because it is too large
Load diff
3293
archived/sites/erp.seahaven.com/logs/database.log.1
Normal file
3293
archived/sites/erp.seahaven.com/logs/database.log.1
Normal file
File diff suppressed because it is too large
Load diff
3088
archived/sites/erp.seahaven.com/logs/database.log.2
Normal file
3088
archived/sites/erp.seahaven.com/logs/database.log.2
Normal file
File diff suppressed because it is too large
Load diff
3039
archived/sites/erp.seahaven.com/logs/database.log.3
Normal file
3039
archived/sites/erp.seahaven.com/logs/database.log.3
Normal file
File diff suppressed because it is too large
Load diff
2978
archived/sites/erp.seahaven.com/logs/database.log.4
Normal file
2978
archived/sites/erp.seahaven.com/logs/database.log.4
Normal file
File diff suppressed because it is too large
Load diff
3079
archived/sites/erp.seahaven.com/logs/database.log.5
Normal file
3079
archived/sites/erp.seahaven.com/logs/database.log.5
Normal file
File diff suppressed because it is too large
Load diff
3276
archived/sites/erp.seahaven.com/logs/database.log.6
Normal file
3276
archived/sites/erp.seahaven.com/logs/database.log.6
Normal file
File diff suppressed because it is too large
Load diff
3144
archived/sites/erp.seahaven.com/logs/database.log.7
Normal file
3144
archived/sites/erp.seahaven.com/logs/database.log.7
Normal file
File diff suppressed because it is too large
Load diff
38
archived/sites/erp.seahaven.com/logs/scheduler.log
Normal file
38
archived/sites/erp.seahaven.com/logs/scheduler.log
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
2025-05-20 13:23:19,732 ERROR scheduler Exception in Enqueue Events for Site erp.seahaven.com
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "/home/pilot/frappe-bench/apps/frappe/frappe/utils/scheduler.py", line 99, in enqueue_events_for_site
|
||||||
|
if is_scheduler_inactive():
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "/home/pilot/frappe-bench/apps/frappe/frappe/utils/scheduler.py", line 143, in is_scheduler_inactive
|
||||||
|
if is_scheduler_disabled(verbose=verbose):
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "/home/pilot/frappe-bench/apps/frappe/frappe/utils/scheduler.py", line 156, in is_scheduler_disabled
|
||||||
|
frappe.db.get_single_value("System Settings", "enable_scheduler")
|
||||||
|
File "/home/pilot/frappe-bench/apps/frappe/frappe/database/database.py", line 837, in get_single_value
|
||||||
|
).run()
|
||||||
|
^^^^^
|
||||||
|
File "/home/pilot/frappe-bench/apps/frappe/frappe/query_builder/utils.py", line 87, in execute_query
|
||||||
|
result = frappe.db.sql(query, params, *args, **kwargs) # nosemgrep
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "/home/pilot/frappe-bench/apps/frappe/frappe/database/database.py", line 230, in sql
|
||||||
|
self._cursor.execute(query, values)
|
||||||
|
File "/home/pilot/frappe-bench/env/lib/python3.12/site-packages/pymysql/cursors.py", line 153, in execute
|
||||||
|
result = self._query(query)
|
||||||
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
File "/home/pilot/frappe-bench/env/lib/python3.12/site-packages/pymysql/cursors.py", line 322, in _query
|
||||||
|
conn.query(q)
|
||||||
|
File "/home/pilot/frappe-bench/env/lib/python3.12/site-packages/pymysql/connections.py", line 563, in query
|
||||||
|
self._affected_rows = self._read_query_result(unbuffered=unbuffered)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "/home/pilot/frappe-bench/env/lib/python3.12/site-packages/pymysql/connections.py", line 825, in _read_query_result
|
||||||
|
result.read()
|
||||||
|
File "/home/pilot/frappe-bench/env/lib/python3.12/site-packages/pymysql/connections.py", line 1199, in read
|
||||||
|
first_packet = self.connection._read_packet()
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "/home/pilot/frappe-bench/env/lib/python3.12/site-packages/pymysql/connections.py", line 775, in _read_packet
|
||||||
|
packet.raise_for_error()
|
||||||
|
File "/home/pilot/frappe-bench/env/lib/python3.12/site-packages/pymysql/protocol.py", line 219, in raise_for_error
|
||||||
|
err.raise_mysql_exception(self._data)
|
||||||
|
File "/home/pilot/frappe-bench/env/lib/python3.12/site-packages/pymysql/err.py", line 150, in raise_mysql_exception
|
||||||
|
raise errorclass(errno, errval)
|
||||||
|
pymysql.err.ProgrammingError: (1146, "Table '_dd79dbfb116c2426.tabSingles' doesn't exist")
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"db_name": "_dd79dbfb116c2426",
|
||||||
|
"db_password": "KJUUmx78uW63Wpg2",
|
||||||
|
"db_type": "mariadb",
|
||||||
|
"pause_scheduler": false,
|
||||||
|
"user_type_doctype_limit": {
|
||||||
|
"employee_self_service": 40
|
||||||
|
}
|
||||||
|
}
|
||||||
9
archived/sites/erp.seahaven.com/site_config.json
Normal file
9
archived/sites/erp.seahaven.com/site_config.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"db_name": "_dd79dbfb116c2426",
|
||||||
|
"db_password": "KJUUmx78uW63Wpg2",
|
||||||
|
"db_type": "mariadb",
|
||||||
|
"pause_scheduler": false,
|
||||||
|
"user_type_doctype_limit": {
|
||||||
|
"employee_self_service": 40
|
||||||
|
}
|
||||||
|
}
|
||||||
0
config/bench_build.lock
Normal file
0
config/bench_build.lock
Normal file
160
config/nginx.conf
Normal file
160
config/nginx.conf
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
upstream frappe-bench-frappe {
|
||||||
|
server 127.0.0.1:8000 fail_timeout=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream frappe-bench-socketio-server {
|
||||||
|
server 127.0.0.1:9000 fail_timeout=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# setup maps
|
||||||
|
|
||||||
|
|
||||||
|
# server blocks
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
server {
|
||||||
|
|
||||||
|
listen 443 ssl;
|
||||||
|
listen [::]:443 ssl;
|
||||||
|
|
||||||
|
|
||||||
|
server_name
|
||||||
|
accounting.seahaven.com
|
||||||
|
;
|
||||||
|
|
||||||
|
root /home/pilot/frappe-bench/sites;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
proxy_buffer_size 128k;
|
||||||
|
proxy_buffers 4 256k;
|
||||||
|
proxy_busy_buffers_size 256k;
|
||||||
|
|
||||||
|
|
||||||
|
ssl_certificate /home/pilot/sites/ssl/seahaven_wc_chain.crt;
|
||||||
|
ssl_certificate_key /home/pilot/sites/ssl/seahaven.key;
|
||||||
|
ssl_session_timeout 5m;
|
||||||
|
ssl_session_cache shared:SSL:10m;
|
||||||
|
ssl_session_tickets off;
|
||||||
|
ssl_stapling on;
|
||||||
|
ssl_stapling_verify on;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
|
||||||
|
ssl_ecdh_curve secp384r1;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
|
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN";
|
||||||
|
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
|
||||||
|
add_header X-Content-Type-Options nosniff;
|
||||||
|
add_header X-XSS-Protection "1; mode=block";
|
||||||
|
add_header Referrer-Policy "same-origin, strict-origin-when-cross-origin";
|
||||||
|
|
||||||
|
location /assets {
|
||||||
|
try_files $uri =404;
|
||||||
|
add_header Cache-Control "max-age=31536000";
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ ^/protected/(.*) {
|
||||||
|
internal;
|
||||||
|
try_files /$host/$1 =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /socket.io {
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header X-Frappe-Site-Name $host;
|
||||||
|
proxy_set_header Origin $scheme://$http_host;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
|
||||||
|
proxy_pass http://frappe-bench-socketio-server;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
|
||||||
|
rewrite ^(.+)/$ $1 permanent;
|
||||||
|
rewrite ^(.+)/index\.html$ $1 permanent;
|
||||||
|
rewrite ^(.+)\.html$ $1 permanent;
|
||||||
|
|
||||||
|
location ~* ^/files/.*.(htm|html|svg|xml) {
|
||||||
|
add_header Content-disposition "attachment";
|
||||||
|
try_files /$host/public/$uri @webserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
try_files /$host/public/$uri @webserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
location @webserver {
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Frappe-Site-Name $host;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Use-X-Accel-Redirect True;
|
||||||
|
proxy_read_timeout 120;
|
||||||
|
proxy_redirect off;
|
||||||
|
|
||||||
|
proxy_pass http://frappe-bench-frappe;
|
||||||
|
}
|
||||||
|
|
||||||
|
# error pages
|
||||||
|
error_page 502 /502.html;
|
||||||
|
location /502.html {
|
||||||
|
root /usr/local/lib/python3.12/dist-packages/bench/config/templates;
|
||||||
|
internal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# optimizations
|
||||||
|
sendfile on;
|
||||||
|
keepalive_timeout 15;
|
||||||
|
client_max_body_size 50m;
|
||||||
|
client_body_buffer_size 16K;
|
||||||
|
client_header_buffer_size 1k;
|
||||||
|
|
||||||
|
# enable gzip compresion
|
||||||
|
# based on https://mattstauffer.co/blog/enabling-gzip-on-nginx-servers-including-laravel-forge
|
||||||
|
gzip on;
|
||||||
|
gzip_http_version 1.1;
|
||||||
|
gzip_comp_level 5;
|
||||||
|
gzip_min_length 256;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_types
|
||||||
|
application/atom+xml
|
||||||
|
application/javascript
|
||||||
|
application/json
|
||||||
|
application/rss+xml
|
||||||
|
application/vnd.ms-fontobject
|
||||||
|
application/x-font-ttf
|
||||||
|
application/font-woff
|
||||||
|
application/x-web-app-manifest+json
|
||||||
|
application/xhtml+xml
|
||||||
|
application/xml
|
||||||
|
font/opentype
|
||||||
|
image/svg+xml
|
||||||
|
image/x-icon
|
||||||
|
text/css
|
||||||
|
text/plain
|
||||||
|
text/x-component
|
||||||
|
;
|
||||||
|
# text/html is always compressed by HttpGzipModule
|
||||||
|
}
|
||||||
|
|
||||||
|
# http to https redirect
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name
|
||||||
|
accounting.seahaven.com
|
||||||
|
;
|
||||||
|
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
1
config/pids/redis_cache.pid
Normal file
1
config/pids/redis_cache.pid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
347152
|
||||||
1
config/pids/redis_queue.pid
Normal file
1
config/pids/redis_queue.pid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
347153
|
||||||
BIN
config/pids/redis_queue.rdb
Normal file
BIN
config/pids/redis_queue.rdb
Normal file
Binary file not shown.
0
config/redis_cache.acl
Normal file
0
config/redis_cache.acl
Normal file
13
config/redis_cache.conf
Normal file
13
config/redis_cache.conf
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
dbfilename redis_cache.rdb
|
||||||
|
dir /home/pilot/frappe-bench/config/pids
|
||||||
|
pidfile /home/pilot/frappe-bench/config/pids/redis_cache.pid
|
||||||
|
bind 127.0.0.1
|
||||||
|
port 13000
|
||||||
|
maxmemory 191mb
|
||||||
|
maxmemory-policy allkeys-lru
|
||||||
|
appendonly no
|
||||||
|
|
||||||
|
save ""
|
||||||
|
|
||||||
|
|
||||||
|
aclfile /home/pilot/frappe-bench/config/redis_cache.acl
|
||||||
0
config/redis_queue.acl
Normal file
0
config/redis_queue.acl
Normal file
7
config/redis_queue.conf
Normal file
7
config/redis_queue.conf
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
dbfilename redis_queue.rdb
|
||||||
|
dir /home/pilot/frappe-bench/config/pids
|
||||||
|
pidfile /home/pilot/frappe-bench/config/pids/redis_queue.pid
|
||||||
|
bind 127.0.0.1
|
||||||
|
port 11000
|
||||||
|
|
||||||
|
aclfile /home/pilot/frappe-bench/config/redis_queue.acl
|
||||||
0
config/scheduler_process
Normal file
0
config/scheduler_process
Normal file
0
config/site_config.lock
Normal file
0
config/site_config.lock
Normal file
114
config/supervisor.conf
Normal file
114
config/supervisor.conf
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
; Notes:
|
||||||
|
; priority=1 --> Lower priorities indicate programs that start first and shut down last
|
||||||
|
; killasgroup=true --> send kill signal to child processes too
|
||||||
|
|
||||||
|
; graceful timeout should always be lower than stopwaitsecs to avoid orphan gunicorn workers.
|
||||||
|
[program:frappe-bench-frappe-web]
|
||||||
|
command=/home/pilot/frappe-bench/env/bin/gunicorn -b 127.0.0.1:8000 -w 5 --max-requests 5000 --max-requests-jitter 500 -t 120 --graceful-timeout 30 frappe.app:application --preload
|
||||||
|
priority=4
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stdout_logfile=/home/pilot/frappe-bench/logs/web.log
|
||||||
|
stderr_logfile=/home/pilot/frappe-bench/logs/web.error.log
|
||||||
|
stopwaitsecs=40
|
||||||
|
killasgroup=true
|
||||||
|
user=pilot
|
||||||
|
directory=/home/pilot/frappe-bench/sites
|
||||||
|
startretries=10
|
||||||
|
|
||||||
|
[program:frappe-bench-frappe-schedule]
|
||||||
|
command=/usr/local/bin/bench schedule
|
||||||
|
priority=3
|
||||||
|
autostart=true
|
||||||
|
stdout_logfile=/home/pilot/frappe-bench/logs/schedule.log
|
||||||
|
stderr_logfile=/home/pilot/frappe-bench/logs/schedule.error.log
|
||||||
|
user=pilot
|
||||||
|
directory=/home/pilot/frappe-bench
|
||||||
|
startretries=10
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[program:frappe-bench-frappe-short-worker]
|
||||||
|
command=/usr/local/bin/bench worker --queue short,default
|
||||||
|
priority=4
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stdout_logfile=/home/pilot/frappe-bench/logs/worker.log
|
||||||
|
stderr_logfile=/home/pilot/frappe-bench/logs/worker.error.log
|
||||||
|
user=pilot
|
||||||
|
stopwaitsecs=360
|
||||||
|
directory=/home/pilot/frappe-bench
|
||||||
|
killasgroup=true
|
||||||
|
numprocs=1
|
||||||
|
process_name=%(program_name)s-%(process_num)d
|
||||||
|
startretries=10
|
||||||
|
|
||||||
|
[program:frappe-bench-frappe-long-worker]
|
||||||
|
command=/usr/local/bin/bench worker --queue long,default,short
|
||||||
|
priority=4
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stdout_logfile=/home/pilot/frappe-bench/logs/worker.log
|
||||||
|
stderr_logfile=/home/pilot/frappe-bench/logs/worker.error.log
|
||||||
|
user=pilot
|
||||||
|
stopwaitsecs=1560
|
||||||
|
directory=/home/pilot/frappe-bench
|
||||||
|
killasgroup=true
|
||||||
|
numprocs=1
|
||||||
|
process_name=%(program_name)s-%(process_num)d
|
||||||
|
startretries=10
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[program:frappe-bench-redis-cache]
|
||||||
|
command=/usr/bin/redis-server /home/pilot/frappe-bench/config/redis_cache.conf
|
||||||
|
priority=1
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stdout_logfile=/home/pilot/frappe-bench/logs/redis-cache.log
|
||||||
|
stderr_logfile=/home/pilot/frappe-bench/logs/redis-cache.error.log
|
||||||
|
user=pilot
|
||||||
|
directory=/home/pilot/frappe-bench/sites
|
||||||
|
startretries=10
|
||||||
|
|
||||||
|
[program:frappe-bench-redis-queue]
|
||||||
|
command=/usr/bin/redis-server /home/pilot/frappe-bench/config/redis_queue.conf
|
||||||
|
priority=1
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stdout_logfile=/home/pilot/frappe-bench/logs/redis-queue.log
|
||||||
|
stderr_logfile=/home/pilot/frappe-bench/logs/redis-queue.error.log
|
||||||
|
user=pilot
|
||||||
|
directory=/home/pilot/frappe-bench/sites
|
||||||
|
startretries=10
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[program:frappe-bench-node-socketio]
|
||||||
|
command=/usr/bin/node /home/pilot/frappe-bench/apps/frappe/socketio.js
|
||||||
|
priority=4
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stdout_logfile=/home/pilot/frappe-bench/logs/node-socketio.log
|
||||||
|
stderr_logfile=/home/pilot/frappe-bench/logs/node-socketio.error.log
|
||||||
|
user=pilot
|
||||||
|
directory=/home/pilot/frappe-bench
|
||||||
|
startretries=10
|
||||||
|
|
||||||
|
|
||||||
|
[group:frappe-bench-web]
|
||||||
|
programs=frappe-bench-frappe-web,frappe-bench-node-socketio
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[group:frappe-bench-workers]
|
||||||
|
programs=frappe-bench-frappe-schedule,frappe-bench-frappe-short-worker,frappe-bench-frappe-long-worker
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[group:frappe-bench-redis]
|
||||||
|
programs=frappe-bench-redis-cache,frappe-bench-redis-queue
|
||||||
247
env/bin/Activate.ps1
vendored
Normal file
247
env/bin/Activate.ps1
vendored
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
<#
|
||||||
|
.Synopsis
|
||||||
|
Activate a Python virtual environment for the current PowerShell session.
|
||||||
|
|
||||||
|
.Description
|
||||||
|
Pushes the python executable for a virtual environment to the front of the
|
||||||
|
$Env:PATH environment variable and sets the prompt to signify that you are
|
||||||
|
in a Python virtual environment. Makes use of the command line switches as
|
||||||
|
well as the `pyvenv.cfg` file values present in the virtual environment.
|
||||||
|
|
||||||
|
.Parameter VenvDir
|
||||||
|
Path to the directory that contains the virtual environment to activate. The
|
||||||
|
default value for this is the parent of the directory that the Activate.ps1
|
||||||
|
script is located within.
|
||||||
|
|
||||||
|
.Parameter Prompt
|
||||||
|
The prompt prefix to display when this virtual environment is activated. By
|
||||||
|
default, this prompt is the name of the virtual environment folder (VenvDir)
|
||||||
|
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
||||||
|
|
||||||
|
.Example
|
||||||
|
Activate.ps1
|
||||||
|
Activates the Python virtual environment that contains the Activate.ps1 script.
|
||||||
|
|
||||||
|
.Example
|
||||||
|
Activate.ps1 -Verbose
|
||||||
|
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||||
|
and shows extra information about the activation as it executes.
|
||||||
|
|
||||||
|
.Example
|
||||||
|
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
||||||
|
Activates the Python virtual environment located in the specified location.
|
||||||
|
|
||||||
|
.Example
|
||||||
|
Activate.ps1 -Prompt "MyPython"
|
||||||
|
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||||
|
and prefixes the current prompt with the specified string (surrounded in
|
||||||
|
parentheses) while the virtual environment is active.
|
||||||
|
|
||||||
|
.Notes
|
||||||
|
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
||||||
|
execution policy for the user. You can do this by issuing the following PowerShell
|
||||||
|
command:
|
||||||
|
|
||||||
|
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||||
|
|
||||||
|
For more information on Execution Policies:
|
||||||
|
https://go.microsoft.com/fwlink/?LinkID=135170
|
||||||
|
|
||||||
|
#>
|
||||||
|
Param(
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[String]
|
||||||
|
$VenvDir,
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[String]
|
||||||
|
$Prompt
|
||||||
|
)
|
||||||
|
|
||||||
|
<# Function declarations --------------------------------------------------- #>
|
||||||
|
|
||||||
|
<#
|
||||||
|
.Synopsis
|
||||||
|
Remove all shell session elements added by the Activate script, including the
|
||||||
|
addition of the virtual environment's Python executable from the beginning of
|
||||||
|
the PATH variable.
|
||||||
|
|
||||||
|
.Parameter NonDestructive
|
||||||
|
If present, do not remove this function from the global namespace for the
|
||||||
|
session.
|
||||||
|
|
||||||
|
#>
|
||||||
|
function global:deactivate ([switch]$NonDestructive) {
|
||||||
|
# Revert to original values
|
||||||
|
|
||||||
|
# The prior prompt:
|
||||||
|
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
|
||||||
|
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
|
||||||
|
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
|
||||||
|
}
|
||||||
|
|
||||||
|
# The prior PYTHONHOME:
|
||||||
|
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
|
||||||
|
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
|
||||||
|
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
|
||||||
|
}
|
||||||
|
|
||||||
|
# The prior PATH:
|
||||||
|
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
|
||||||
|
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
|
||||||
|
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
# Just remove the VIRTUAL_ENV altogether:
|
||||||
|
if (Test-Path -Path Env:VIRTUAL_ENV) {
|
||||||
|
Remove-Item -Path env:VIRTUAL_ENV
|
||||||
|
}
|
||||||
|
|
||||||
|
# Just remove VIRTUAL_ENV_PROMPT altogether.
|
||||||
|
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
|
||||||
|
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
|
||||||
|
}
|
||||||
|
|
||||||
|
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
|
||||||
|
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
||||||
|
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
# Leave deactivate function in the global namespace if requested:
|
||||||
|
if (-not $NonDestructive) {
|
||||||
|
Remove-Item -Path function:deactivate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.Description
|
||||||
|
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
|
||||||
|
given folder, and returns them in a map.
|
||||||
|
|
||||||
|
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
|
||||||
|
two strings separated by `=` (with any amount of whitespace surrounding the =)
|
||||||
|
then it is considered a `key = value` line. The left hand string is the key,
|
||||||
|
the right hand is the value.
|
||||||
|
|
||||||
|
If the value starts with a `'` or a `"` then the first and last character is
|
||||||
|
stripped from the value before being captured.
|
||||||
|
|
||||||
|
.Parameter ConfigDir
|
||||||
|
Path to the directory that contains the `pyvenv.cfg` file.
|
||||||
|
#>
|
||||||
|
function Get-PyVenvConfig(
|
||||||
|
[String]
|
||||||
|
$ConfigDir
|
||||||
|
) {
|
||||||
|
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
||||||
|
|
||||||
|
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
|
||||||
|
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
||||||
|
|
||||||
|
# An empty map will be returned if no config file is found.
|
||||||
|
$pyvenvConfig = @{ }
|
||||||
|
|
||||||
|
if ($pyvenvConfigPath) {
|
||||||
|
|
||||||
|
Write-Verbose "File exists, parse `key = value` lines"
|
||||||
|
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
||||||
|
|
||||||
|
$pyvenvConfigContent | ForEach-Object {
|
||||||
|
$keyval = $PSItem -split "\s*=\s*", 2
|
||||||
|
if ($keyval[0] -and $keyval[1]) {
|
||||||
|
$val = $keyval[1]
|
||||||
|
|
||||||
|
# Remove extraneous quotations around a string value.
|
||||||
|
if ("'""".Contains($val.Substring(0, 1))) {
|
||||||
|
$val = $val.Substring(1, $val.Length - 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
$pyvenvConfig[$keyval[0]] = $val
|
||||||
|
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $pyvenvConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
<# Begin Activate script --------------------------------------------------- #>
|
||||||
|
|
||||||
|
# Determine the containing directory of this script
|
||||||
|
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
$VenvExecDir = Get-Item -Path $VenvExecPath
|
||||||
|
|
||||||
|
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
||||||
|
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
|
||||||
|
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
||||||
|
|
||||||
|
# Set values required in priority: CmdLine, ConfigFile, Default
|
||||||
|
# First, get the location of the virtual environment, it might not be
|
||||||
|
# VenvExecDir if specified on the command line.
|
||||||
|
if ($VenvDir) {
|
||||||
|
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
||||||
|
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
||||||
|
Write-Verbose "VenvDir=$VenvDir"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Next, read the `pyvenv.cfg` file to determine any required value such
|
||||||
|
# as `prompt`.
|
||||||
|
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
||||||
|
|
||||||
|
# Next, set the prompt from the command line, or the config file, or
|
||||||
|
# just use the name of the virtual environment folder.
|
||||||
|
if ($Prompt) {
|
||||||
|
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
||||||
|
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
||||||
|
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
||||||
|
$Prompt = $pyvenvCfg['prompt'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
|
||||||
|
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
|
||||||
|
$Prompt = Split-Path -Path $venvDir -Leaf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Verbose "Prompt = '$Prompt'"
|
||||||
|
Write-Verbose "VenvDir='$VenvDir'"
|
||||||
|
|
||||||
|
# Deactivate any currently active virtual environment, but leave the
|
||||||
|
# deactivate function in place.
|
||||||
|
deactivate -nondestructive
|
||||||
|
|
||||||
|
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
|
||||||
|
# that there is an activated venv.
|
||||||
|
$env:VIRTUAL_ENV = $VenvDir
|
||||||
|
|
||||||
|
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
||||||
|
|
||||||
|
Write-Verbose "Setting prompt to '$Prompt'"
|
||||||
|
|
||||||
|
# Set the prompt to include the env name
|
||||||
|
# Make sure _OLD_VIRTUAL_PROMPT is global
|
||||||
|
function global:_OLD_VIRTUAL_PROMPT { "" }
|
||||||
|
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
|
||||||
|
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
||||||
|
|
||||||
|
function global:prompt {
|
||||||
|
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
|
||||||
|
_OLD_VIRTUAL_PROMPT
|
||||||
|
}
|
||||||
|
$env:VIRTUAL_ENV_PROMPT = $Prompt
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clear PYTHONHOME
|
||||||
|
if (Test-Path -Path Env:PYTHONHOME) {
|
||||||
|
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
|
||||||
|
Remove-Item -Path Env:PYTHONHOME
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add the venv to the PATH
|
||||||
|
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
|
||||||
|
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
|
||||||
BIN
env/bin/__pycache__/dumppdf.cpython-312.pyc
vendored
Normal file
BIN
env/bin/__pycache__/dumppdf.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
env/bin/__pycache__/jp.cpython-312.pyc
vendored
Normal file
BIN
env/bin/__pycache__/jp.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
env/bin/__pycache__/pdf2txt.cpython-312.pyc
vendored
Normal file
BIN
env/bin/__pycache__/pdf2txt.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
env/bin/__pycache__/runxlrd.cpython-312.pyc
vendored
Normal file
BIN
env/bin/__pycache__/runxlrd.cpython-312.pyc
vendored
Normal file
Binary file not shown.
70
env/bin/activate
vendored
Normal file
70
env/bin/activate
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
# This file must be used with "source bin/activate" *from bash*
|
||||||
|
# You cannot run it directly
|
||||||
|
|
||||||
|
deactivate () {
|
||||||
|
# reset old environment variables
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||||
|
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||||
|
export PATH
|
||||||
|
unset _OLD_VIRTUAL_PATH
|
||||||
|
fi
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||||
|
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||||
|
export PYTHONHOME
|
||||||
|
unset _OLD_VIRTUAL_PYTHONHOME
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Call hash to forget past commands. Without forgetting
|
||||||
|
# past commands the $PATH changes we made may not be respected
|
||||||
|
hash -r 2> /dev/null
|
||||||
|
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||||
|
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||||
|
export PS1
|
||||||
|
unset _OLD_VIRTUAL_PS1
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset VIRTUAL_ENV
|
||||||
|
unset VIRTUAL_ENV_PROMPT
|
||||||
|
if [ ! "${1:-}" = "nondestructive" ] ; then
|
||||||
|
# Self destruct!
|
||||||
|
unset -f deactivate
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# unset irrelevant variables
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
# on Windows, a path can contain colons and backslashes and has to be converted:
|
||||||
|
if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then
|
||||||
|
# transform D:\path\to\venv to /d/path/to/venv on MSYS
|
||||||
|
# and to /cygdrive/d/path/to/venv on Cygwin
|
||||||
|
export VIRTUAL_ENV=$(cygpath /home/pilot/frappe-bench/env)
|
||||||
|
else
|
||||||
|
# use the path as-is
|
||||||
|
export VIRTUAL_ENV=/home/pilot/frappe-bench/env
|
||||||
|
fi
|
||||||
|
|
||||||
|
_OLD_VIRTUAL_PATH="$PATH"
|
||||||
|
PATH="$VIRTUAL_ENV/"bin":$PATH"
|
||||||
|
export PATH
|
||||||
|
|
||||||
|
# unset PYTHONHOME if set
|
||||||
|
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||||
|
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||||
|
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||||
|
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||||
|
unset PYTHONHOME
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||||
|
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||||
|
PS1='(env) '"${PS1:-}"
|
||||||
|
export PS1
|
||||||
|
VIRTUAL_ENV_PROMPT='(env) '
|
||||||
|
export VIRTUAL_ENV_PROMPT
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Call hash to forget past commands. Without forgetting
|
||||||
|
# past commands the $PATH changes we made may not be respected
|
||||||
|
hash -r 2> /dev/null
|
||||||
27
env/bin/activate.csh
vendored
Normal file
27
env/bin/activate.csh
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||||
|
# You cannot run it directly.
|
||||||
|
|
||||||
|
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||||
|
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
||||||
|
|
||||||
|
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||||
|
|
||||||
|
# Unset irrelevant variables.
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
setenv VIRTUAL_ENV /home/pilot/frappe-bench/env
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PATH="$PATH"
|
||||||
|
setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
|
||||||
|
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||||
|
|
||||||
|
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||||
|
set prompt = '(env) '"$prompt"
|
||||||
|
setenv VIRTUAL_ENV_PROMPT '(env) '
|
||||||
|
endif
|
||||||
|
|
||||||
|
alias pydoc python -m pydoc
|
||||||
|
|
||||||
|
rehash
|
||||||
69
env/bin/activate.fish
vendored
Normal file
69
env/bin/activate.fish
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
|
||||||
|
# (https://fishshell.com/). You cannot run it directly.
|
||||||
|
|
||||||
|
function deactivate -d "Exit virtual environment and return to normal shell environment"
|
||||||
|
# reset old environment variables
|
||||||
|
if test -n "$_OLD_VIRTUAL_PATH"
|
||||||
|
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||||
|
set -e _OLD_VIRTUAL_PATH
|
||||||
|
end
|
||||||
|
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
||||||
|
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
||||||
|
set -e _OLD_VIRTUAL_PYTHONHOME
|
||||||
|
end
|
||||||
|
|
||||||
|
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||||
|
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||||
|
# prevents error when using nested fish instances (Issue #93858)
|
||||||
|
if functions -q _old_fish_prompt
|
||||||
|
functions -e fish_prompt
|
||||||
|
functions -c _old_fish_prompt fish_prompt
|
||||||
|
functions -e _old_fish_prompt
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
set -e VIRTUAL_ENV
|
||||||
|
set -e VIRTUAL_ENV_PROMPT
|
||||||
|
if test "$argv[1]" != "nondestructive"
|
||||||
|
# Self-destruct!
|
||||||
|
functions -e deactivate
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Unset irrelevant variables.
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
set -gx VIRTUAL_ENV /home/pilot/frappe-bench/env
|
||||||
|
|
||||||
|
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||||
|
set -gx PATH "$VIRTUAL_ENV/"bin $PATH
|
||||||
|
|
||||||
|
# Unset PYTHONHOME if set.
|
||||||
|
if set -q PYTHONHOME
|
||||||
|
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||||
|
set -e PYTHONHOME
|
||||||
|
end
|
||||||
|
|
||||||
|
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||||
|
# fish uses a function instead of an env var to generate the prompt.
|
||||||
|
|
||||||
|
# Save the current fish_prompt function as the function _old_fish_prompt.
|
||||||
|
functions -c fish_prompt _old_fish_prompt
|
||||||
|
|
||||||
|
# With the original prompt function renamed, we can override with our own.
|
||||||
|
function fish_prompt
|
||||||
|
# Save the return status of the last command.
|
||||||
|
set -l old_status $status
|
||||||
|
|
||||||
|
# Output the venv prompt; color taken from the blue of the Python logo.
|
||||||
|
printf "%s%s%s" (set_color 4B8BBE) '(env) ' (set_color normal)
|
||||||
|
|
||||||
|
# Restore the return status of the previous command.
|
||||||
|
echo "exit $old_status" | .
|
||||||
|
# Output the original/"old" prompt.
|
||||||
|
_old_fish_prompt
|
||||||
|
end
|
||||||
|
|
||||||
|
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||||
|
set -gx VIRTUAL_ENV_PROMPT '(env) '
|
||||||
|
end
|
||||||
8
env/bin/change_tz
vendored
Executable file
8
env/bin/change_tz
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from vobject.change_tz import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/chardetect
vendored
Executable file
8
env/bin/chardetect
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from chardet.cli.chardetect import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/csscapture
vendored
Executable file
8
env/bin/csscapture
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from cssutils.scripts.csscapture import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/csscombine
vendored
Executable file
8
env/bin/csscombine
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from cssutils.scripts.csscombine import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/cssparse
vendored
Executable file
8
env/bin/cssparse
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from cssutils.scripts.cssparse import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/distro
vendored
Executable file
8
env/bin/distro
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from distro.distro import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/dmypy
vendored
Executable file
8
env/bin/dmypy
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from mypy.dmypy.client import console_entry
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(console_entry())
|
||||||
480
env/bin/dumppdf.py
vendored
Executable file
480
env/bin/dumppdf.py
vendored
Executable file
|
|
@ -0,0 +1,480 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
"""Extract pdf structure in XML format"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os.path
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
from typing import Any, Container, Dict, Iterable, List, Optional, TextIO, Union, cast
|
||||||
|
|
||||||
|
import pdfminer
|
||||||
|
from pdfminer.pdfdocument import PDFDocument, PDFNoOutlines, PDFXRefFallback
|
||||||
|
from pdfminer.pdfexceptions import (
|
||||||
|
PDFIOError,
|
||||||
|
PDFObjectNotFound,
|
||||||
|
PDFTypeError,
|
||||||
|
PDFValueError,
|
||||||
|
)
|
||||||
|
from pdfminer.pdfpage import PDFPage
|
||||||
|
from pdfminer.pdfparser import PDFParser
|
||||||
|
from pdfminer.pdftypes import PDFObjRef, PDFStream, resolve1, stream_value
|
||||||
|
from pdfminer.psparser import LIT, PSKeyword, PSLiteral
|
||||||
|
from pdfminer.utils import isnumber
|
||||||
|
|
||||||
|
logging.basicConfig()
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ESC_PAT = re.compile(r'[\000-\037&<>()"\042\047\134\177-\377]')
|
||||||
|
|
||||||
|
|
||||||
|
def escape(s: Union[str, bytes]) -> str:
|
||||||
|
if isinstance(s, bytes):
|
||||||
|
us = str(s, "latin-1")
|
||||||
|
else:
|
||||||
|
us = s
|
||||||
|
return ESC_PAT.sub(lambda m: "&#%d;" % ord(m.group(0)), us)
|
||||||
|
|
||||||
|
|
||||||
|
def dumpxml(out: TextIO, obj: object, codec: Optional[str] = None) -> None:
|
||||||
|
if obj is None:
|
||||||
|
out.write("<null />")
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
out.write('<dict size="%d">\n' % len(obj))
|
||||||
|
for k, v in obj.items():
|
||||||
|
out.write("<key>%s</key>\n" % k)
|
||||||
|
out.write("<value>")
|
||||||
|
dumpxml(out, v)
|
||||||
|
out.write("</value>\n")
|
||||||
|
out.write("</dict>")
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(obj, list):
|
||||||
|
out.write('<list size="%d">\n' % len(obj))
|
||||||
|
for v in obj:
|
||||||
|
dumpxml(out, v)
|
||||||
|
out.write("\n")
|
||||||
|
out.write("</list>")
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(obj, (str, bytes)):
|
||||||
|
out.write('<string size="%d">%s</string>' % (len(obj), escape(obj)))
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(obj, PDFStream):
|
||||||
|
if codec == "raw":
|
||||||
|
# Bug: writing bytes to text I/O. This will raise TypeError.
|
||||||
|
out.write(obj.get_rawdata()) # type: ignore [arg-type]
|
||||||
|
elif codec == "binary":
|
||||||
|
# Bug: writing bytes to text I/O. This will raise TypeError.
|
||||||
|
out.write(obj.get_data()) # type: ignore [arg-type]
|
||||||
|
else:
|
||||||
|
out.write("<stream>\n<props>\n")
|
||||||
|
dumpxml(out, obj.attrs)
|
||||||
|
out.write("\n</props>\n")
|
||||||
|
if codec == "text":
|
||||||
|
data = obj.get_data()
|
||||||
|
out.write('<data size="%d">%s</data>\n' % (len(data), escape(data)))
|
||||||
|
out.write("</stream>")
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(obj, PDFObjRef):
|
||||||
|
out.write('<ref id="%d" />' % obj.objid)
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(obj, PSKeyword):
|
||||||
|
# Likely bug: obj.name is bytes, not str
|
||||||
|
out.write("<keyword>%s</keyword>" % obj.name) # type: ignore [str-bytes-safe]
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(obj, PSLiteral):
|
||||||
|
# Likely bug: obj.name may be bytes, not str
|
||||||
|
out.write("<literal>%s</literal>" % obj.name) # type: ignore [str-bytes-safe]
|
||||||
|
return
|
||||||
|
|
||||||
|
if isnumber(obj):
|
||||||
|
out.write("<number>%s</number>" % obj)
|
||||||
|
return
|
||||||
|
|
||||||
|
raise PDFTypeError(obj)
|
||||||
|
|
||||||
|
|
||||||
|
def dumptrailers(
|
||||||
|
out: TextIO,
|
||||||
|
doc: PDFDocument,
|
||||||
|
show_fallback_xref: bool = False,
|
||||||
|
) -> None:
|
||||||
|
for xref in doc.xrefs:
|
||||||
|
if not isinstance(xref, PDFXRefFallback) or show_fallback_xref:
|
||||||
|
out.write("<trailer>\n")
|
||||||
|
dumpxml(out, xref.get_trailer())
|
||||||
|
out.write("\n</trailer>\n\n")
|
||||||
|
no_xrefs = all(isinstance(xref, PDFXRefFallback) for xref in doc.xrefs)
|
||||||
|
if no_xrefs and not show_fallback_xref:
|
||||||
|
msg = (
|
||||||
|
"This PDF does not have an xref. Use --show-fallback-xref if "
|
||||||
|
"you want to display the content of a fallback xref that "
|
||||||
|
"contains all objects."
|
||||||
|
)
|
||||||
|
logger.warning(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def dumpallobjs(
|
||||||
|
out: TextIO,
|
||||||
|
doc: PDFDocument,
|
||||||
|
codec: Optional[str] = None,
|
||||||
|
show_fallback_xref: bool = False,
|
||||||
|
) -> None:
|
||||||
|
visited = set()
|
||||||
|
out.write("<pdf>")
|
||||||
|
for xref in doc.xrefs:
|
||||||
|
for objid in xref.get_objids():
|
||||||
|
if objid in visited:
|
||||||
|
continue
|
||||||
|
visited.add(objid)
|
||||||
|
try:
|
||||||
|
obj = doc.getobj(objid)
|
||||||
|
if obj is None:
|
||||||
|
continue
|
||||||
|
out.write('<object id="%d">\n' % objid)
|
||||||
|
dumpxml(out, obj, codec=codec)
|
||||||
|
out.write("\n</object>\n\n")
|
||||||
|
except PDFObjectNotFound as e:
|
||||||
|
print("not found: %r" % e)
|
||||||
|
dumptrailers(out, doc, show_fallback_xref)
|
||||||
|
out.write("</pdf>")
|
||||||
|
|
||||||
|
|
||||||
|
def dumpoutline(
|
||||||
|
outfp: TextIO,
|
||||||
|
fname: str,
|
||||||
|
objids: Any,
|
||||||
|
pagenos: Container[int],
|
||||||
|
password: str = "",
|
||||||
|
dumpall: bool = False,
|
||||||
|
codec: Optional[str] = None,
|
||||||
|
extractdir: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
fp = open(fname, "rb")
|
||||||
|
parser = PDFParser(fp)
|
||||||
|
doc = PDFDocument(parser, password)
|
||||||
|
pages = {
|
||||||
|
page.pageid: pageno
|
||||||
|
for (pageno, page) in enumerate(PDFPage.create_pages(doc), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
def resolve_dest(dest: object) -> Any:
|
||||||
|
if isinstance(dest, (str, bytes)):
|
||||||
|
dest = resolve1(doc.get_dest(dest))
|
||||||
|
elif isinstance(dest, PSLiteral):
|
||||||
|
dest = resolve1(doc.get_dest(dest.name))
|
||||||
|
if isinstance(dest, dict):
|
||||||
|
dest = dest["D"]
|
||||||
|
if isinstance(dest, PDFObjRef):
|
||||||
|
dest = dest.resolve()
|
||||||
|
return dest
|
||||||
|
|
||||||
|
try:
|
||||||
|
outlines = doc.get_outlines()
|
||||||
|
outfp.write("<outlines>\n")
|
||||||
|
for level, title, dest, a, se in outlines:
|
||||||
|
pageno = None
|
||||||
|
if dest:
|
||||||
|
dest = resolve_dest(dest)
|
||||||
|
pageno = pages[dest[0].objid]
|
||||||
|
elif a:
|
||||||
|
action = a
|
||||||
|
if isinstance(action, dict):
|
||||||
|
subtype = action.get("S")
|
||||||
|
if subtype and repr(subtype) == "/'GoTo'" and action.get("D"):
|
||||||
|
dest = resolve_dest(action["D"])
|
||||||
|
pageno = pages[dest[0].objid]
|
||||||
|
s = escape(title)
|
||||||
|
outfp.write(f'<outline level="{level!r}" title="{s}">\n')
|
||||||
|
if dest is not None:
|
||||||
|
outfp.write("<dest>")
|
||||||
|
dumpxml(outfp, dest)
|
||||||
|
outfp.write("</dest>\n")
|
||||||
|
if pageno is not None:
|
||||||
|
outfp.write("<pageno>%r</pageno>\n" % pageno)
|
||||||
|
outfp.write("</outline>\n")
|
||||||
|
outfp.write("</outlines>\n")
|
||||||
|
except PDFNoOutlines:
|
||||||
|
pass
|
||||||
|
parser.close()
|
||||||
|
fp.close()
|
||||||
|
|
||||||
|
|
||||||
|
LITERAL_FILESPEC = LIT("Filespec")
|
||||||
|
LITERAL_EMBEDDEDFILE = LIT("EmbeddedFile")
|
||||||
|
|
||||||
|
|
||||||
|
def extractembedded(fname: str, password: str, extractdir: str) -> None:
|
||||||
|
def extract1(objid: int, obj: Dict[str, Any]) -> None:
|
||||||
|
filename = os.path.basename(obj.get("UF") or cast(bytes, obj.get("F")).decode())
|
||||||
|
fileref = obj["EF"].get("UF") or obj["EF"].get("F")
|
||||||
|
fileobj = doc.getobj(fileref.objid)
|
||||||
|
if not isinstance(fileobj, PDFStream):
|
||||||
|
error_msg = (
|
||||||
|
"unable to process PDF: reference for %r is not a "
|
||||||
|
"PDFStream" % filename
|
||||||
|
)
|
||||||
|
raise PDFValueError(error_msg)
|
||||||
|
if fileobj.get("Type") is not LITERAL_EMBEDDEDFILE:
|
||||||
|
raise PDFValueError(
|
||||||
|
"unable to process PDF: reference for %r "
|
||||||
|
"is not an EmbeddedFile" % (filename),
|
||||||
|
)
|
||||||
|
path = os.path.join(extractdir, "%.6d-%s" % (objid, filename))
|
||||||
|
if os.path.exists(path):
|
||||||
|
raise PDFIOError("file exists: %r" % path)
|
||||||
|
print("extracting: %r" % path)
|
||||||
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||||
|
out = open(path, "wb")
|
||||||
|
out.write(fileobj.get_data())
|
||||||
|
out.close()
|
||||||
|
|
||||||
|
with open(fname, "rb") as fp:
|
||||||
|
parser = PDFParser(fp)
|
||||||
|
doc = PDFDocument(parser, password)
|
||||||
|
extracted_objids = set()
|
||||||
|
for xref in doc.xrefs:
|
||||||
|
for objid in xref.get_objids():
|
||||||
|
obj = doc.getobj(objid)
|
||||||
|
if (
|
||||||
|
objid not in extracted_objids
|
||||||
|
and isinstance(obj, dict)
|
||||||
|
and obj.get("Type") is LITERAL_FILESPEC
|
||||||
|
):
|
||||||
|
extracted_objids.add(objid)
|
||||||
|
extract1(objid, obj)
|
||||||
|
|
||||||
|
|
||||||
|
def dumppdf(
|
||||||
|
outfp: TextIO,
|
||||||
|
fname: str,
|
||||||
|
objids: Iterable[int],
|
||||||
|
pagenos: Container[int],
|
||||||
|
password: str = "",
|
||||||
|
dumpall: bool = False,
|
||||||
|
codec: Optional[str] = None,
|
||||||
|
extractdir: Optional[str] = None,
|
||||||
|
show_fallback_xref: bool = False,
|
||||||
|
) -> None:
|
||||||
|
fp = open(fname, "rb")
|
||||||
|
parser = PDFParser(fp)
|
||||||
|
doc = PDFDocument(parser, password)
|
||||||
|
if objids:
|
||||||
|
for objid in objids:
|
||||||
|
obj = doc.getobj(objid)
|
||||||
|
dumpxml(outfp, obj, codec=codec)
|
||||||
|
if pagenos:
|
||||||
|
for pageno, page in enumerate(PDFPage.create_pages(doc)):
|
||||||
|
if pageno in pagenos:
|
||||||
|
if codec:
|
||||||
|
for obj in page.contents:
|
||||||
|
obj = stream_value(obj)
|
||||||
|
dumpxml(outfp, obj, codec=codec)
|
||||||
|
else:
|
||||||
|
dumpxml(outfp, page.attrs)
|
||||||
|
if dumpall:
|
||||||
|
dumpallobjs(outfp, doc, codec, show_fallback_xref)
|
||||||
|
if (not objids) and (not pagenos) and (not dumpall):
|
||||||
|
dumptrailers(outfp, doc, show_fallback_xref)
|
||||||
|
fp.close()
|
||||||
|
if codec not in ("raw", "binary"):
|
||||||
|
outfp.write("\n")
|
||||||
|
|
||||||
|
|
||||||
|
def create_parser() -> ArgumentParser:
|
||||||
|
parser = ArgumentParser(description=__doc__, add_help=True)
|
||||||
|
parser.add_argument(
|
||||||
|
"files",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
nargs="+",
|
||||||
|
help="One or more paths to PDF files.",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--version",
|
||||||
|
"-v",
|
||||||
|
action="version",
|
||||||
|
version=f"pdfminer.six v{pdfminer.__version__}",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--debug",
|
||||||
|
"-d",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="Use debug logging level.",
|
||||||
|
)
|
||||||
|
procedure_parser = parser.add_mutually_exclusive_group()
|
||||||
|
procedure_parser.add_argument(
|
||||||
|
"--extract-toc",
|
||||||
|
"-T",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="Extract structure of outline",
|
||||||
|
)
|
||||||
|
procedure_parser.add_argument(
|
||||||
|
"--extract-embedded",
|
||||||
|
"-E",
|
||||||
|
type=str,
|
||||||
|
help="Extract embedded files",
|
||||||
|
)
|
||||||
|
|
||||||
|
parse_params = parser.add_argument_group(
|
||||||
|
"Parser",
|
||||||
|
description="Used during PDF parsing",
|
||||||
|
)
|
||||||
|
parse_params.add_argument(
|
||||||
|
"--page-numbers",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
nargs="+",
|
||||||
|
help="A space-seperated list of page numbers to parse.",
|
||||||
|
)
|
||||||
|
parse_params.add_argument(
|
||||||
|
"--pagenos",
|
||||||
|
"-p",
|
||||||
|
type=str,
|
||||||
|
help="A comma-separated list of page numbers to parse. Included for "
|
||||||
|
"legacy applications, use --page-numbers for more idiomatic "
|
||||||
|
"argument entry.",
|
||||||
|
)
|
||||||
|
parse_params.add_argument(
|
||||||
|
"--objects",
|
||||||
|
"-i",
|
||||||
|
type=str,
|
||||||
|
help="Comma separated list of object numbers to extract",
|
||||||
|
)
|
||||||
|
parse_params.add_argument(
|
||||||
|
"--all",
|
||||||
|
"-a",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="If the structure of all objects should be extracted",
|
||||||
|
)
|
||||||
|
parse_params.add_argument(
|
||||||
|
"--show-fallback-xref",
|
||||||
|
action="store_true",
|
||||||
|
help="Additionally show the fallback xref. Use this if the PDF "
|
||||||
|
"has zero or only invalid xref's. This setting is ignored if "
|
||||||
|
"--extract-toc or --extract-embedded is used.",
|
||||||
|
)
|
||||||
|
parse_params.add_argument(
|
||||||
|
"--password",
|
||||||
|
"-P",
|
||||||
|
type=str,
|
||||||
|
default="",
|
||||||
|
help="The password to use for decrypting PDF file.",
|
||||||
|
)
|
||||||
|
|
||||||
|
output_params = parser.add_argument_group(
|
||||||
|
"Output",
|
||||||
|
description="Used during output generation.",
|
||||||
|
)
|
||||||
|
output_params.add_argument(
|
||||||
|
"--outfile",
|
||||||
|
"-o",
|
||||||
|
type=str,
|
||||||
|
default="-",
|
||||||
|
help='Path to file where output is written. Or "-" (default) to '
|
||||||
|
"write to stdout.",
|
||||||
|
)
|
||||||
|
codec_parser = output_params.add_mutually_exclusive_group()
|
||||||
|
codec_parser.add_argument(
|
||||||
|
"--raw-stream",
|
||||||
|
"-r",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="Write stream objects without encoding",
|
||||||
|
)
|
||||||
|
codec_parser.add_argument(
|
||||||
|
"--binary-stream",
|
||||||
|
"-b",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="Write stream objects with binary encoding",
|
||||||
|
)
|
||||||
|
codec_parser.add_argument(
|
||||||
|
"--text-stream",
|
||||||
|
"-t",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="Write stream objects as plain text",
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: Optional[List[str]] = None) -> None:
|
||||||
|
parser = create_parser()
|
||||||
|
args = parser.parse_args(args=argv)
|
||||||
|
|
||||||
|
if args.debug:
|
||||||
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
if args.outfile == "-":
|
||||||
|
outfp = sys.stdout
|
||||||
|
else:
|
||||||
|
outfp = open(args.outfile, "w")
|
||||||
|
|
||||||
|
if args.objects:
|
||||||
|
objids = [int(x) for x in args.objects.split(",")]
|
||||||
|
else:
|
||||||
|
objids = []
|
||||||
|
|
||||||
|
if args.page_numbers:
|
||||||
|
pagenos = {x - 1 for x in args.page_numbers}
|
||||||
|
elif args.pagenos:
|
||||||
|
pagenos = {int(x) - 1 for x in args.pagenos.split(",")}
|
||||||
|
else:
|
||||||
|
pagenos = set()
|
||||||
|
|
||||||
|
password = args.password
|
||||||
|
|
||||||
|
if args.raw_stream:
|
||||||
|
codec: Optional[str] = "raw"
|
||||||
|
elif args.binary_stream:
|
||||||
|
codec = "binary"
|
||||||
|
elif args.text_stream:
|
||||||
|
codec = "text"
|
||||||
|
else:
|
||||||
|
codec = None
|
||||||
|
|
||||||
|
for fname in args.files:
|
||||||
|
if args.extract_toc:
|
||||||
|
dumpoutline(
|
||||||
|
outfp,
|
||||||
|
fname,
|
||||||
|
objids,
|
||||||
|
pagenos,
|
||||||
|
password=password,
|
||||||
|
dumpall=args.all,
|
||||||
|
codec=codec,
|
||||||
|
extractdir=None,
|
||||||
|
)
|
||||||
|
elif args.extract_embedded:
|
||||||
|
extractembedded(fname, password=password, extractdir=args.extract_embedded)
|
||||||
|
else:
|
||||||
|
dumppdf(
|
||||||
|
outfp,
|
||||||
|
fname,
|
||||||
|
objids,
|
||||||
|
pagenos,
|
||||||
|
password=password,
|
||||||
|
dumpall=args.all,
|
||||||
|
codec=codec,
|
||||||
|
extractdir=None,
|
||||||
|
show_fallback_xref=args.show_fallback_xref,
|
||||||
|
)
|
||||||
|
|
||||||
|
outfp.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
8
env/bin/filetype
vendored
Executable file
8
env/bin/filetype
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from filetype.__main__ import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/fonttools
vendored
Executable file
8
env/bin/fonttools
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from fontTools.__main__ import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/google-oauthlib-tool
vendored
Executable file
8
env/bin/google-oauthlib-tool
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from google_auth_oauthlib.tool.__main__ import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/gunicorn
vendored
Executable file
8
env/bin/gunicorn
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from gunicorn.app.wsgiapp import run
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(run())
|
||||||
8
env/bin/ics_diff
vendored
Executable file
8
env/bin/ics_diff
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from vobject.ics_diff import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/ipython
vendored
Executable file
8
env/bin/ipython
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from IPython import start_ipython
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(start_ipython())
|
||||||
8
env/bin/ipython3
vendored
Executable file
8
env/bin/ipython3
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from IPython import start_ipython
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(start_ipython())
|
||||||
54
env/bin/jp.py
vendored
Executable file
54
env/bin/jp.py
vendored
Executable file
|
|
@ -0,0 +1,54 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import argparse
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
import jmespath
|
||||||
|
from jmespath import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('expression')
|
||||||
|
parser.add_argument('-f', '--filename',
|
||||||
|
help=('The filename containing the input data. '
|
||||||
|
'If a filename is not given then data is '
|
||||||
|
'read from stdin.'))
|
||||||
|
parser.add_argument('--ast', action='store_true',
|
||||||
|
help=('Pretty print the AST, do not search the data.'))
|
||||||
|
args = parser.parse_args()
|
||||||
|
expression = args.expression
|
||||||
|
if args.ast:
|
||||||
|
# Only print the AST
|
||||||
|
expression = jmespath.compile(args.expression)
|
||||||
|
sys.stdout.write(pformat(expression.parsed))
|
||||||
|
sys.stdout.write('\n')
|
||||||
|
return 0
|
||||||
|
if args.filename:
|
||||||
|
with open(args.filename, 'r') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
else:
|
||||||
|
data = sys.stdin.read()
|
||||||
|
data = json.loads(data)
|
||||||
|
try:
|
||||||
|
sys.stdout.write(json.dumps(
|
||||||
|
jmespath.search(expression, data), indent=4, ensure_ascii=False))
|
||||||
|
sys.stdout.write('\n')
|
||||||
|
except exceptions.ArityError as e:
|
||||||
|
sys.stderr.write("invalid-arity: %s\n" % e)
|
||||||
|
return 1
|
||||||
|
except exceptions.JMESPathTypeError as e:
|
||||||
|
sys.stderr.write("invalid-type: %s\n" % e)
|
||||||
|
return 1
|
||||||
|
except exceptions.UnknownFunctionError as e:
|
||||||
|
sys.stderr.write("unknown-function: %s\n" % e)
|
||||||
|
return 1
|
||||||
|
except exceptions.ParseError as e:
|
||||||
|
sys.stderr.write("syntax-error: %s\n" % e)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/markdown2
vendored
Executable file
8
env/bin/markdown2
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from markdown2 import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/markdownify
vendored
Executable file
8
env/bin/markdownify
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from markdownify.main import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/mypy
vendored
Executable file
8
env/bin/mypy
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from mypy.__main__ import console_entry
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(console_entry())
|
||||||
8
env/bin/mypyc
vendored
Executable file
8
env/bin/mypyc
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from mypyc.__main__ import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/normalizer
vendored
Executable file
8
env/bin/normalizer
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from charset_normalizer import cli
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(cli.cli_detect())
|
||||||
95
env/bin/num2words
vendored
Executable file
95
env/bin/num2words
vendored
Executable file
|
|
@ -0,0 +1,95 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
|
||||||
|
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
|
||||||
|
|
||||||
|
# This library is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2.1 of the License, or (at your option) any later version.
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
# MA 02110-1301 USA
|
||||||
|
|
||||||
|
"""num2words: convert numbers into words.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
num2words [options] <number>
|
||||||
|
num2words --list-languages
|
||||||
|
num2words --list-converters
|
||||||
|
num2words --help
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
<number> Number you want to convert into words
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-L --list-languages Show all languages.
|
||||||
|
-C --list-converters Show all converters.
|
||||||
|
-l --lang=<lang> Output language [default: en].
|
||||||
|
-t --to=<to> Output converter [default: cardinal].
|
||||||
|
-h --help Show this message.
|
||||||
|
-v --version Show version.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$ num2words 10001
|
||||||
|
ten thousand and one
|
||||||
|
|
||||||
|
$ num2words 24,120.10
|
||||||
|
twenty-four thousand, one hundred and twenty point one
|
||||||
|
|
||||||
|
$ num2words 24,120.10 -l es
|
||||||
|
veinticuatro mil ciento veinte punto uno
|
||||||
|
|
||||||
|
$num2words 2.14 -l es --to currency
|
||||||
|
dos euros con catorce céntimos
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import print_function, unicode_literals
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from docopt import docopt
|
||||||
|
import num2words
|
||||||
|
|
||||||
|
__version__ = "0.5.14"
|
||||||
|
__license__ = "LGPL"
|
||||||
|
|
||||||
|
|
||||||
|
def get_languages():
|
||||||
|
return sorted(list(num2words.CONVERTER_CLASSES.keys()))
|
||||||
|
|
||||||
|
|
||||||
|
def get_converters():
|
||||||
|
return sorted(list(num2words.CONVERTES_TYPES))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
version = "{}=={}".format(os.path.basename(__file__), __version__)
|
||||||
|
args = docopt(__doc__, argv=None, help=True, version=version, options_first=False)
|
||||||
|
if args["--list-languages"]:
|
||||||
|
for lang in get_languages():
|
||||||
|
sys.stdout.write(lang)
|
||||||
|
sys.stdout.write(os.linesep)
|
||||||
|
sys.exit(0)
|
||||||
|
if args["--list-converters"]:
|
||||||
|
for cvt in get_converters():
|
||||||
|
sys.stdout.write(cvt)
|
||||||
|
sys.stdout.write(os.linesep)
|
||||||
|
sys.exit(0)
|
||||||
|
try:
|
||||||
|
words = num2words.num2words(args['<number>'], lang=args['--lang'], to=args['--to'])
|
||||||
|
sys.stdout.write(words + os.linesep)
|
||||||
|
sys.exit(0)
|
||||||
|
except Exception as err:
|
||||||
|
sys.stderr.write(str(args['<number>']))
|
||||||
|
sys.stderr.write(str(err) + os.linesep)
|
||||||
|
sys.stderr.write(__doc__)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
323
env/bin/pdf2txt.py
vendored
Executable file
323
env/bin/pdf2txt.py
vendored
Executable file
|
|
@ -0,0 +1,323 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
"""A command line tool for extracting text and images from PDF and
|
||||||
|
output it to plain text, html, xml or tags.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
from typing import Any, Container, Iterable, List, Optional
|
||||||
|
|
||||||
|
import pdfminer.high_level
|
||||||
|
from pdfminer.layout import LAParams
|
||||||
|
from pdfminer.pdfexceptions import PDFValueError
|
||||||
|
from pdfminer.utils import AnyIO
|
||||||
|
|
||||||
|
logging.basicConfig()
|
||||||
|
|
||||||
|
OUTPUT_TYPES = ((".htm", "html"), (".html", "html"), (".xml", "xml"), (".tag", "tag"))
|
||||||
|
|
||||||
|
|
||||||
|
def float_or_disabled(x: str) -> Optional[float]:
|
||||||
|
if x.lower().strip() == "disabled":
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return float(x)
|
||||||
|
except ValueError:
|
||||||
|
raise argparse.ArgumentTypeError(f"invalid float value: {x}")
|
||||||
|
|
||||||
|
|
||||||
|
def extract_text(
|
||||||
|
files: Iterable[str] = [],
|
||||||
|
outfile: str = "-",
|
||||||
|
laparams: Optional[LAParams] = None,
|
||||||
|
output_type: str = "text",
|
||||||
|
codec: str = "utf-8",
|
||||||
|
strip_control: bool = False,
|
||||||
|
maxpages: int = 0,
|
||||||
|
page_numbers: Optional[Container[int]] = None,
|
||||||
|
password: str = "",
|
||||||
|
scale: float = 1.0,
|
||||||
|
rotation: int = 0,
|
||||||
|
layoutmode: str = "normal",
|
||||||
|
output_dir: Optional[str] = None,
|
||||||
|
debug: bool = False,
|
||||||
|
disable_caching: bool = False,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> AnyIO:
|
||||||
|
if not files:
|
||||||
|
raise PDFValueError("Must provide files to work upon!")
|
||||||
|
|
||||||
|
if output_type == "text" and outfile != "-":
|
||||||
|
for override, alttype in OUTPUT_TYPES:
|
||||||
|
if outfile.endswith(override):
|
||||||
|
output_type = alttype
|
||||||
|
|
||||||
|
if outfile == "-":
|
||||||
|
outfp: AnyIO = sys.stdout
|
||||||
|
if sys.stdout.encoding is not None:
|
||||||
|
codec = "utf-8"
|
||||||
|
else:
|
||||||
|
outfp = open(outfile, "wb")
|
||||||
|
|
||||||
|
for fname in files:
|
||||||
|
with open(fname, "rb") as fp:
|
||||||
|
pdfminer.high_level.extract_text_to_fp(fp, **locals())
|
||||||
|
return outfp
|
||||||
|
|
||||||
|
|
||||||
|
def create_parser() -> argparse.ArgumentParser:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__, add_help=True)
|
||||||
|
parser.add_argument(
|
||||||
|
"files",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
nargs="+",
|
||||||
|
help="One or more paths to PDF files.",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--version",
|
||||||
|
"-v",
|
||||||
|
action="version",
|
||||||
|
version=f"pdfminer.six v{pdfminer.__version__}",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--debug",
|
||||||
|
"-d",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="Use debug logging level.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--disable-caching",
|
||||||
|
"-C",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="If caching or resources, such as fonts, should be disabled.",
|
||||||
|
)
|
||||||
|
|
||||||
|
parse_params = parser.add_argument_group(
|
||||||
|
"Parser",
|
||||||
|
description="Used during PDF parsing",
|
||||||
|
)
|
||||||
|
parse_params.add_argument(
|
||||||
|
"--page-numbers",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
nargs="+",
|
||||||
|
help="A space-seperated list of page numbers to parse.",
|
||||||
|
)
|
||||||
|
parse_params.add_argument(
|
||||||
|
"--pagenos",
|
||||||
|
"-p",
|
||||||
|
type=str,
|
||||||
|
help="A comma-separated list of page numbers to parse. "
|
||||||
|
"Included for legacy applications, use --page-numbers "
|
||||||
|
"for more idiomatic argument entry.",
|
||||||
|
)
|
||||||
|
parse_params.add_argument(
|
||||||
|
"--maxpages",
|
||||||
|
"-m",
|
||||||
|
type=int,
|
||||||
|
default=0,
|
||||||
|
help="The maximum number of pages to parse.",
|
||||||
|
)
|
||||||
|
parse_params.add_argument(
|
||||||
|
"--password",
|
||||||
|
"-P",
|
||||||
|
type=str,
|
||||||
|
default="",
|
||||||
|
help="The password to use for decrypting PDF file.",
|
||||||
|
)
|
||||||
|
parse_params.add_argument(
|
||||||
|
"--rotation",
|
||||||
|
"-R",
|
||||||
|
default=0,
|
||||||
|
type=int,
|
||||||
|
help="The number of degrees to rotate the PDF "
|
||||||
|
"before other types of processing.",
|
||||||
|
)
|
||||||
|
|
||||||
|
la_params = LAParams() # will be used for defaults
|
||||||
|
la_param_group = parser.add_argument_group(
|
||||||
|
"Layout analysis",
|
||||||
|
description="Used during layout analysis.",
|
||||||
|
)
|
||||||
|
la_param_group.add_argument(
|
||||||
|
"--no-laparams",
|
||||||
|
"-n",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="If layout analysis parameters should be ignored.",
|
||||||
|
)
|
||||||
|
la_param_group.add_argument(
|
||||||
|
"--detect-vertical",
|
||||||
|
"-V",
|
||||||
|
default=la_params.detect_vertical,
|
||||||
|
action="store_true",
|
||||||
|
help="If vertical text should be considered during layout analysis",
|
||||||
|
)
|
||||||
|
la_param_group.add_argument(
|
||||||
|
"--line-overlap",
|
||||||
|
type=float,
|
||||||
|
default=la_params.line_overlap,
|
||||||
|
help="If two characters have more overlap than this they "
|
||||||
|
"are considered to be on the same line. The overlap is specified "
|
||||||
|
"relative to the minimum height of both characters.",
|
||||||
|
)
|
||||||
|
la_param_group.add_argument(
|
||||||
|
"--char-margin",
|
||||||
|
"-M",
|
||||||
|
type=float,
|
||||||
|
default=la_params.char_margin,
|
||||||
|
help="If two characters are closer together than this margin they "
|
||||||
|
"are considered to be part of the same line. The margin is "
|
||||||
|
"specified relative to the width of the character.",
|
||||||
|
)
|
||||||
|
la_param_group.add_argument(
|
||||||
|
"--word-margin",
|
||||||
|
"-W",
|
||||||
|
type=float,
|
||||||
|
default=la_params.word_margin,
|
||||||
|
help="If two characters on the same line are further apart than this "
|
||||||
|
"margin then they are considered to be two separate words, and "
|
||||||
|
"an intermediate space will be added for readability. The margin "
|
||||||
|
"is specified relative to the width of the character.",
|
||||||
|
)
|
||||||
|
la_param_group.add_argument(
|
||||||
|
"--line-margin",
|
||||||
|
"-L",
|
||||||
|
type=float,
|
||||||
|
default=la_params.line_margin,
|
||||||
|
help="If two lines are close together they are considered to "
|
||||||
|
"be part of the same paragraph. The margin is specified "
|
||||||
|
"relative to the height of a line.",
|
||||||
|
)
|
||||||
|
la_param_group.add_argument(
|
||||||
|
"--boxes-flow",
|
||||||
|
"-F",
|
||||||
|
type=float_or_disabled,
|
||||||
|
default=la_params.boxes_flow,
|
||||||
|
help="Specifies how much a horizontal and vertical position of a "
|
||||||
|
"text matters when determining the order of lines. The value "
|
||||||
|
"should be within the range of -1.0 (only horizontal position "
|
||||||
|
"matters) to +1.0 (only vertical position matters). You can also "
|
||||||
|
"pass `disabled` to disable advanced layout analysis, and "
|
||||||
|
"instead return text based on the position of the bottom left "
|
||||||
|
"corner of the text box.",
|
||||||
|
)
|
||||||
|
la_param_group.add_argument(
|
||||||
|
"--all-texts",
|
||||||
|
"-A",
|
||||||
|
default=la_params.all_texts,
|
||||||
|
action="store_true",
|
||||||
|
help="If layout analysis should be performed on text in figures.",
|
||||||
|
)
|
||||||
|
|
||||||
|
output_params = parser.add_argument_group(
|
||||||
|
"Output",
|
||||||
|
description="Used during output generation.",
|
||||||
|
)
|
||||||
|
output_params.add_argument(
|
||||||
|
"--outfile",
|
||||||
|
"-o",
|
||||||
|
type=str,
|
||||||
|
default="-",
|
||||||
|
help="Path to file where output is written. "
|
||||||
|
'Or "-" (default) to write to stdout.',
|
||||||
|
)
|
||||||
|
output_params.add_argument(
|
||||||
|
"--output_type",
|
||||||
|
"-t",
|
||||||
|
type=str,
|
||||||
|
default="text",
|
||||||
|
help="Type of output to generate {text,html,xml,tag}.",
|
||||||
|
)
|
||||||
|
output_params.add_argument(
|
||||||
|
"--codec",
|
||||||
|
"-c",
|
||||||
|
type=str,
|
||||||
|
default="utf-8",
|
||||||
|
help="Text encoding to use in output file.",
|
||||||
|
)
|
||||||
|
output_params.add_argument(
|
||||||
|
"--output-dir",
|
||||||
|
"-O",
|
||||||
|
default=None,
|
||||||
|
help="The output directory to put extracted images in. If not given, "
|
||||||
|
"images are not extracted.",
|
||||||
|
)
|
||||||
|
output_params.add_argument(
|
||||||
|
"--layoutmode",
|
||||||
|
"-Y",
|
||||||
|
default="normal",
|
||||||
|
type=str,
|
||||||
|
help="Type of layout to use when generating html "
|
||||||
|
"{normal,exact,loose}. If normal,each line is"
|
||||||
|
" positioned separately in the html. If exact"
|
||||||
|
", each character is positioned separately in"
|
||||||
|
" the html. If loose, same result as normal "
|
||||||
|
"but with an additional newline after each "
|
||||||
|
"text line. Only used when output_type is html.",
|
||||||
|
)
|
||||||
|
output_params.add_argument(
|
||||||
|
"--scale",
|
||||||
|
"-s",
|
||||||
|
type=float,
|
||||||
|
default=1.0,
|
||||||
|
help="The amount of zoom to use when generating html file. "
|
||||||
|
"Only used when output_type is html.",
|
||||||
|
)
|
||||||
|
output_params.add_argument(
|
||||||
|
"--strip-control",
|
||||||
|
"-S",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="Remove control statement from text. "
|
||||||
|
"Only used when output_type is xml.",
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args(args: Optional[List[str]]) -> argparse.Namespace:
|
||||||
|
parsed_args = create_parser().parse_args(args=args)
|
||||||
|
|
||||||
|
# Propagate parsed layout parameters to LAParams object
|
||||||
|
if parsed_args.no_laparams:
|
||||||
|
parsed_args.laparams = None
|
||||||
|
else:
|
||||||
|
parsed_args.laparams = LAParams(
|
||||||
|
line_overlap=parsed_args.line_overlap,
|
||||||
|
char_margin=parsed_args.char_margin,
|
||||||
|
line_margin=parsed_args.line_margin,
|
||||||
|
word_margin=parsed_args.word_margin,
|
||||||
|
boxes_flow=parsed_args.boxes_flow,
|
||||||
|
detect_vertical=parsed_args.detect_vertical,
|
||||||
|
all_texts=parsed_args.all_texts,
|
||||||
|
)
|
||||||
|
|
||||||
|
if parsed_args.page_numbers:
|
||||||
|
parsed_args.page_numbers = {x - 1 for x in parsed_args.page_numbers}
|
||||||
|
|
||||||
|
if parsed_args.pagenos:
|
||||||
|
parsed_args.page_numbers = {int(x) - 1 for x in parsed_args.pagenos.split(",")}
|
||||||
|
|
||||||
|
if parsed_args.output_type == "text" and parsed_args.outfile != "-":
|
||||||
|
for override, alttype in OUTPUT_TYPES:
|
||||||
|
if parsed_args.outfile.endswith(override):
|
||||||
|
parsed_args.output_type = alttype
|
||||||
|
|
||||||
|
return parsed_args
|
||||||
|
|
||||||
|
|
||||||
|
def main(args: Optional[List[str]] = None) -> int:
|
||||||
|
parsed_args = parse_args(args)
|
||||||
|
outfp = extract_text(**vars(parsed_args))
|
||||||
|
outfp.close()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/pdfplumber
vendored
Executable file
8
env/bin/pdfplumber
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pdfplumber.cli import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/pip
vendored
Executable file
8
env/bin/pip
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pip._internal.cli.main import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/pip3
vendored
Executable file
8
env/bin/pip3
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pip._internal.cli.main import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/pip3.12
vendored
Executable file
8
env/bin/pip3.12
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pip._internal.cli.main import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
266
env/bin/prichunkpng
vendored
Executable file
266
env/bin/prichunkpng
vendored
Executable file
|
|
@ -0,0 +1,266 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# prichunkpng
|
||||||
|
# Chunk editing tool.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Make a new PNG by adding, delete, or replacing particular chunks.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import collections
|
||||||
|
|
||||||
|
# https://docs.python.org/2.7/library/io.html
|
||||||
|
import io
|
||||||
|
import re
|
||||||
|
import string
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import zlib
|
||||||
|
|
||||||
|
# Local module.
|
||||||
|
import png
|
||||||
|
|
||||||
|
|
||||||
|
Chunk = collections.namedtuple("Chunk", "type content")
|
||||||
|
|
||||||
|
|
||||||
|
class ArgumentError(Exception):
|
||||||
|
"""A user problem with the command arguments."""
|
||||||
|
|
||||||
|
|
||||||
|
def process(out, args):
|
||||||
|
"""Process the PNG file args.input to the output, chunk by chunk.
|
||||||
|
Chunks can be inserted, removed, replaced, or sometimes edited.
|
||||||
|
Chunks are specified by their 4 byte Chunk Type;
|
||||||
|
see https://www.w3.org/TR/2003/REC-PNG-20031110/#5Chunk-layout .
|
||||||
|
The chunks in args.delete will be removed from the stream.
|
||||||
|
The chunks in args.chunk will be inserted into the stream
|
||||||
|
with their contents taken from the named files.
|
||||||
|
|
||||||
|
Other options on the args object will create particular
|
||||||
|
ancillary chunks.
|
||||||
|
|
||||||
|
.gamma -> gAMA chunk
|
||||||
|
.sigbit -> sBIT chunk
|
||||||
|
|
||||||
|
Chunk types need not be official PNG chunks at all.
|
||||||
|
Non-standard chunks can be created.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Convert options to chunks in the args.chunk list
|
||||||
|
if args.gamma:
|
||||||
|
v = int(round(1e5 * args.gamma))
|
||||||
|
bs = io.BytesIO(struct.pack(">I", v))
|
||||||
|
args.chunk.insert(0, Chunk(b"gAMA", bs))
|
||||||
|
if args.sigbit:
|
||||||
|
v = struct.pack("%dB" % len(args.sigbit), *args.sigbit)
|
||||||
|
bs = io.BytesIO(v)
|
||||||
|
args.chunk.insert(0, Chunk(b"sBIT", bs))
|
||||||
|
if args.iccprofile:
|
||||||
|
# http://www.w3.org/TR/PNG/#11iCCP
|
||||||
|
v = b"a color profile\x00\x00" + zlib.compress(args.iccprofile.read())
|
||||||
|
bs = io.BytesIO(v)
|
||||||
|
args.chunk.insert(0, Chunk(b"iCCP", bs))
|
||||||
|
if args.transparent:
|
||||||
|
# https://www.w3.org/TR/2003/REC-PNG-20031110/#11tRNS
|
||||||
|
v = struct.pack(">%dH" % len(args.transparent), *args.transparent)
|
||||||
|
bs = io.BytesIO(v)
|
||||||
|
args.chunk.insert(0, Chunk(b"tRNS", bs))
|
||||||
|
if args.background:
|
||||||
|
# https://www.w3.org/TR/2003/REC-PNG-20031110/#11bKGD
|
||||||
|
v = struct.pack(">%dH" % len(args.background), *args.background)
|
||||||
|
bs = io.BytesIO(v)
|
||||||
|
args.chunk.insert(0, Chunk(b"bKGD", bs))
|
||||||
|
if args.physical:
|
||||||
|
# https://www.w3.org/TR/PNG/#11pHYs
|
||||||
|
numbers = re.findall(r"(\d+\.?\d*)", args.physical)
|
||||||
|
if len(numbers) not in {1, 2}:
|
||||||
|
raise ArgumentError("One or two numbers are required for --physical")
|
||||||
|
xppu = float(numbers[0])
|
||||||
|
if len(numbers) == 1:
|
||||||
|
yppu = xppu
|
||||||
|
else:
|
||||||
|
yppu = float(numbers[1])
|
||||||
|
|
||||||
|
unit_spec = 0
|
||||||
|
if args.physical.endswith("dpi"):
|
||||||
|
# Convert from DPI to Pixels Per Metre
|
||||||
|
# 1 inch is 0.0254 metres
|
||||||
|
l = 0.0254
|
||||||
|
xppu /= l
|
||||||
|
yppu /= l
|
||||||
|
unit_spec = 1
|
||||||
|
elif args.physical.endswith("ppm"):
|
||||||
|
unit_spec = 1
|
||||||
|
|
||||||
|
v = struct.pack("!LLB", round(xppu), round(yppu), unit_spec)
|
||||||
|
bs = io.BytesIO(v)
|
||||||
|
args.chunk.insert(0, Chunk(b"pHYs", bs))
|
||||||
|
|
||||||
|
# Create:
|
||||||
|
# - a set of chunks to delete
|
||||||
|
# - a dict of chunks to replace
|
||||||
|
# - a list of chunk to add
|
||||||
|
|
||||||
|
delete = set(args.delete)
|
||||||
|
# The set of chunks to replace are those where the specification says
|
||||||
|
# that there should be at most one of them.
|
||||||
|
replacing = set([b"gAMA", b"pHYs", b"sBIT", b"PLTE", b"tRNS", b"sPLT", b"IHDR"])
|
||||||
|
replace = dict()
|
||||||
|
add = []
|
||||||
|
|
||||||
|
for chunk in args.chunk:
|
||||||
|
if chunk.type in replacing:
|
||||||
|
replace[chunk.type] = chunk
|
||||||
|
else:
|
||||||
|
add.append(chunk)
|
||||||
|
|
||||||
|
input = png.Reader(file=args.input)
|
||||||
|
|
||||||
|
return png.write_chunks(out, edit_chunks(input.chunks(), delete, replace, add))
|
||||||
|
|
||||||
|
|
||||||
|
def edit_chunks(chunks, delete, replace, add):
|
||||||
|
"""
|
||||||
|
Iterate over chunks, yielding edited chunks.
|
||||||
|
Subtle: the new chunks have to have their contents .read().
|
||||||
|
"""
|
||||||
|
for type, v in chunks:
|
||||||
|
if type in delete:
|
||||||
|
continue
|
||||||
|
if type in replace:
|
||||||
|
yield type, replace[type].content.read()
|
||||||
|
del replace[type]
|
||||||
|
continue
|
||||||
|
|
||||||
|
if b"IDAT" <= type <= b"IDAT" and replace:
|
||||||
|
# If there are any chunks on the replace list by
|
||||||
|
# the time we reach IDAT, add then all now.
|
||||||
|
# put them all on the add list.
|
||||||
|
for chunk in replace.values():
|
||||||
|
yield chunk.type, chunk.content.read()
|
||||||
|
replace = dict()
|
||||||
|
|
||||||
|
if b"IDAT" <= type <= b"IDAT" and add:
|
||||||
|
# We reached IDAT; add all remaining chunks now.
|
||||||
|
for chunk in add:
|
||||||
|
yield chunk.type, chunk.content.read()
|
||||||
|
add = []
|
||||||
|
|
||||||
|
yield type, v
|
||||||
|
|
||||||
|
|
||||||
|
def chunk_name(s):
|
||||||
|
"""
|
||||||
|
Type check a chunk name option value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# See https://www.w3.org/TR/2003/REC-PNG-20031110/#table51
|
||||||
|
valid = len(s) == 4 and set(s) <= set(string.ascii_letters)
|
||||||
|
if not valid:
|
||||||
|
raise ValueError("Chunk name must be 4 ASCII letters")
|
||||||
|
return s.encode("ascii")
|
||||||
|
|
||||||
|
|
||||||
|
def comma_list(s):
|
||||||
|
"""
|
||||||
|
Convert s, a command separated list of whole numbers,
|
||||||
|
into a sequence of int.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return tuple(int(v) for v in s.split(","))
|
||||||
|
|
||||||
|
|
||||||
|
def hex_color(s):
|
||||||
|
"""
|
||||||
|
Type check and convert a hex color.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if s.startswith("#"):
|
||||||
|
s = s[1:]
|
||||||
|
valid = len(s) in [1, 2, 3, 4, 6, 12] and set(s) <= set(string.hexdigits)
|
||||||
|
if not valid:
|
||||||
|
raise ValueError("colour must be 1,2,3,4,6, or 12 hex-digits")
|
||||||
|
|
||||||
|
# For the 4-bit RGB, expand to 8-bit, by repeating digits.
|
||||||
|
if len(s) == 3:
|
||||||
|
s = "".join(c + c for c in s)
|
||||||
|
|
||||||
|
if len(s) in [1, 2, 4]:
|
||||||
|
# Single grey value.
|
||||||
|
return (int(s, 16),)
|
||||||
|
|
||||||
|
if len(s) in [6, 12]:
|
||||||
|
w = len(s) // 3
|
||||||
|
return tuple(int(s[i : i + w], 16) for i in range(0, len(s), w))
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
if argv is None:
|
||||||
|
argv = sys.argv
|
||||||
|
|
||||||
|
argv = argv[1:]
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--gamma", type=float, help="Gamma value for gAMA chunk")
|
||||||
|
parser.add_argument(
|
||||||
|
"--physical",
|
||||||
|
type=str,
|
||||||
|
metavar="x[,y][dpi|ppm]",
|
||||||
|
help="specify intended pixel size or aspect ratio",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--sigbit",
|
||||||
|
type=comma_list,
|
||||||
|
metavar="D[,D[,D[,D]]]",
|
||||||
|
help="Number of significant bits in each channel",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--iccprofile",
|
||||||
|
metavar="file.iccp",
|
||||||
|
type=argparse.FileType("rb"),
|
||||||
|
help="add an ICC Profile from a file",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--transparent",
|
||||||
|
type=hex_color,
|
||||||
|
metavar="#RRGGBB",
|
||||||
|
help="Specify the colour that is transparent (tRNS chunk)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--background",
|
||||||
|
type=hex_color,
|
||||||
|
metavar="#RRGGBB",
|
||||||
|
help="background colour for bKGD chunk",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--delete",
|
||||||
|
action="append",
|
||||||
|
default=[],
|
||||||
|
type=chunk_name,
|
||||||
|
help="delete the chunk",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--chunk",
|
||||||
|
action="append",
|
||||||
|
nargs=2,
|
||||||
|
default=[],
|
||||||
|
type=str,
|
||||||
|
help="insert chunk, taking contents from file",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"input", nargs="?", default="-", type=png.cli_open, metavar="PNG"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
# Reprocess the chunk arguments, converting each pair into a Chunk.
|
||||||
|
args.chunk = [
|
||||||
|
Chunk(chunk_name(type), open(path, "rb")) for type, path in args.chunk
|
||||||
|
]
|
||||||
|
|
||||||
|
return process(png.binary_stdout(), args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
81
env/bin/pricolpng
vendored
Executable file
81
env/bin/pricolpng
vendored
Executable file
|
|
@ -0,0 +1,81 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
|
||||||
|
# http://www.python.org/doc/2.4.4/lib/module-itertools.html
|
||||||
|
import itertools
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import png
|
||||||
|
|
||||||
|
Description = """Join PNG images in a column top-to-bottom."""
|
||||||
|
|
||||||
|
|
||||||
|
class FormatError(Exception):
|
||||||
|
"""
|
||||||
|
Some problem with the image format.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def join_col(out, l):
|
||||||
|
"""
|
||||||
|
Join the list of images.
|
||||||
|
All input images must be same width and
|
||||||
|
have the same number of channels.
|
||||||
|
They are joined top-to-bottom.
|
||||||
|
`out` is the (open file) destination for the output image.
|
||||||
|
`l` should be a list of open files (the input image files).
|
||||||
|
"""
|
||||||
|
|
||||||
|
image = 0
|
||||||
|
stream = 0
|
||||||
|
|
||||||
|
# When the first image is read, this will be the reference width,
|
||||||
|
# which must be the same for all images.
|
||||||
|
width = None
|
||||||
|
# Total height (accumulated as images are read).
|
||||||
|
height = 0
|
||||||
|
# Accumulated rows.
|
||||||
|
rows = []
|
||||||
|
|
||||||
|
for f in l:
|
||||||
|
stream += 1
|
||||||
|
while True:
|
||||||
|
im = png.Reader(file=f)
|
||||||
|
try:
|
||||||
|
im.preamble()
|
||||||
|
except EOFError:
|
||||||
|
break
|
||||||
|
image += 1
|
||||||
|
|
||||||
|
if not width:
|
||||||
|
width = im.width
|
||||||
|
elif width != im.width:
|
||||||
|
raise FormatError('Image %d in stream %d has width %d; does not match %d.' %
|
||||||
|
(image, stream, im.width, width))
|
||||||
|
|
||||||
|
height += im.height
|
||||||
|
# Various bugs here because different numbers of channels and depths go wrong.
|
||||||
|
w, h, p, info = im.asDirect()
|
||||||
|
rows.extend(p)
|
||||||
|
|
||||||
|
# Alarmingly re-use the last info object.
|
||||||
|
tinfo = dict(info)
|
||||||
|
del tinfo['size']
|
||||||
|
w = png.Writer(width, height, **tinfo)
|
||||||
|
|
||||||
|
w.write(out, rows)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description=Description)
|
||||||
|
parser.add_argument(
|
||||||
|
"input", nargs="*", default="-", type=png.cli_open, metavar="PNG"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
return join_col(png.binary_stdout(), args.input)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv)
|
||||||
254
env/bin/priditherpng
vendored
Executable file
254
env/bin/priditherpng
vendored
Executable file
|
|
@ -0,0 +1,254 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
|
||||||
|
# pipdither
|
||||||
|
# Error Diffusing image dithering.
|
||||||
|
# Now with serpentine scanning.
|
||||||
|
|
||||||
|
# See http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
|
||||||
|
|
||||||
|
# http://www.python.org/doc/2.4.4/lib/module-bisect.html
|
||||||
|
from bisect import bisect_left
|
||||||
|
|
||||||
|
|
||||||
|
import png
|
||||||
|
|
||||||
|
|
||||||
|
def dither(
|
||||||
|
out,
|
||||||
|
input,
|
||||||
|
bitdepth=1,
|
||||||
|
linear=False,
|
||||||
|
defaultgamma=1.0,
|
||||||
|
targetgamma=None,
|
||||||
|
cutoff=0.5, # see :cutoff:default
|
||||||
|
):
|
||||||
|
"""Dither the input PNG `inp` into an image with a smaller bit depth
|
||||||
|
and write the result image onto `out`. `bitdepth` specifies the bit
|
||||||
|
depth of the new image.
|
||||||
|
|
||||||
|
Normally the source image gamma is honoured (the image is
|
||||||
|
converted into a linear light space before being dithered), but
|
||||||
|
if the `linear` argument is true then the image is treated as
|
||||||
|
being linear already: no gamma conversion is done (this is
|
||||||
|
quicker, and if you don't care much about accuracy, it won't
|
||||||
|
matter much).
|
||||||
|
|
||||||
|
Images with no gamma indication (no ``gAMA`` chunk) are normally
|
||||||
|
treated as linear (gamma = 1.0), but often it can be better
|
||||||
|
to assume a different gamma value: For example continuous tone
|
||||||
|
photographs intended for presentation on the web often carry
|
||||||
|
an implicit assumption of being encoded with a gamma of about
|
||||||
|
0.45 (because that's what you get if you just "blat the pixels"
|
||||||
|
onto a PC framebuffer), so ``defaultgamma=0.45`` might be a
|
||||||
|
good idea. `defaultgamma` does not override a gamma value
|
||||||
|
specified in the file itself: It is only used when the file
|
||||||
|
does not specify a gamma.
|
||||||
|
|
||||||
|
If you (pointlessly) specify both `linear` and `defaultgamma`,
|
||||||
|
`linear` wins.
|
||||||
|
|
||||||
|
The gamma of the output image is, by default, the same as the input
|
||||||
|
image. The `targetgamma` argument can be used to specify a
|
||||||
|
different gamma for the output image. This effectively recodes the
|
||||||
|
image to a different gamma, dithering as we go. The gamma specified
|
||||||
|
is the exponent used to encode the output file (and appears in the
|
||||||
|
output PNG's ``gAMA`` chunk); it is usually less than 1.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Encoding is what happened when the PNG was made (and also what
|
||||||
|
# happens when we output the PNG). Decoding is what we do to the
|
||||||
|
# source PNG in order to process it.
|
||||||
|
|
||||||
|
# The dithering algorithm is not completely general; it
|
||||||
|
# can only do bit depth reduction, not arbitrary palette changes.
|
||||||
|
import operator
|
||||||
|
|
||||||
|
maxval = 2 ** bitdepth - 1
|
||||||
|
r = png.Reader(file=input)
|
||||||
|
|
||||||
|
_, _, pixels, info = r.asDirect()
|
||||||
|
planes = info["planes"]
|
||||||
|
# :todo: make an Exception
|
||||||
|
assert planes == 1
|
||||||
|
width = info["size"][0]
|
||||||
|
sourcemaxval = 2 ** info["bitdepth"] - 1
|
||||||
|
|
||||||
|
if linear:
|
||||||
|
gamma = 1
|
||||||
|
else:
|
||||||
|
gamma = info.get("gamma") or defaultgamma
|
||||||
|
|
||||||
|
# Calculate an effective gamma for input and output;
|
||||||
|
# then build tables using those.
|
||||||
|
|
||||||
|
# `gamma` (whether it was obtained from the input file or an
|
||||||
|
# assumed value) is the encoding gamma.
|
||||||
|
# We need the decoding gamma, which is the reciprocal.
|
||||||
|
decode = 1.0 / gamma
|
||||||
|
|
||||||
|
# `targetdecode` is the assumed gamma that is going to be used
|
||||||
|
# to decoding the target PNG.
|
||||||
|
# Note that even though we will _encode_ the target PNG we
|
||||||
|
# still need the decoding gamma, because
|
||||||
|
# the table we use maps from PNG pixel value to linear light level.
|
||||||
|
if targetgamma is None:
|
||||||
|
targetdecode = decode
|
||||||
|
else:
|
||||||
|
targetdecode = 1.0 / targetgamma
|
||||||
|
|
||||||
|
incode = build_decode_table(sourcemaxval, decode)
|
||||||
|
|
||||||
|
# For encoding, we still build a decode table, because we
|
||||||
|
# use it inverted (searching with bisect).
|
||||||
|
outcode = build_decode_table(maxval, targetdecode)
|
||||||
|
|
||||||
|
# The table used for choosing output codes. These values represent
|
||||||
|
# the cutoff points between two adjacent output codes.
|
||||||
|
# The cutoff parameter can be varied between 0 and 1 to
|
||||||
|
# preferentially choose lighter (when cutoff > 0.5) or
|
||||||
|
# darker (when cutoff < 0.5) values.
|
||||||
|
# :cutoff:default: The default for this used to be 0.75, but
|
||||||
|
# testing by drj on 2021-07-30 showed that this produces
|
||||||
|
# banding when dithering left-to-right gradients;
|
||||||
|
# test with:
|
||||||
|
# priforgepng grl | priditherpng | kitty icat
|
||||||
|
choosecode = list(zip(outcode[1:], outcode))
|
||||||
|
p = cutoff
|
||||||
|
choosecode = [x[0] * p + x[1] * (1.0 - p) for x in choosecode]
|
||||||
|
|
||||||
|
rows = repeat_header(pixels)
|
||||||
|
dithered_rows = run_dither(incode, choosecode, outcode, width, rows)
|
||||||
|
dithered_rows = remove_header(dithered_rows)
|
||||||
|
|
||||||
|
info["bitdepth"] = bitdepth
|
||||||
|
info["gamma"] = 1.0 / targetdecode
|
||||||
|
w = png.Writer(**info)
|
||||||
|
w.write(out, dithered_rows)
|
||||||
|
|
||||||
|
|
||||||
|
def build_decode_table(maxval, gamma):
|
||||||
|
"""Build a lookup table for decoding;
|
||||||
|
table converts from pixel values to linear space.
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert maxval == int(maxval)
|
||||||
|
assert maxval > 0
|
||||||
|
|
||||||
|
f = 1.0 / maxval
|
||||||
|
table = [f * v for v in range(maxval + 1)]
|
||||||
|
if gamma != 1.0:
|
||||||
|
table = [v ** gamma for v in table]
|
||||||
|
return table
|
||||||
|
|
||||||
|
|
||||||
|
def run_dither(incode, choosecode, outcode, width, rows):
|
||||||
|
"""
|
||||||
|
Run an serpentine dither.
|
||||||
|
Using the incode and choosecode tables.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Errors diffused downwards (into next row)
|
||||||
|
ed = [0.0] * width
|
||||||
|
flipped = False
|
||||||
|
for row in rows:
|
||||||
|
# Convert to linear...
|
||||||
|
row = [incode[v] for v in row]
|
||||||
|
# Add errors...
|
||||||
|
row = [e + v for e, v in zip(ed, row)]
|
||||||
|
|
||||||
|
if flipped:
|
||||||
|
row = row[::-1]
|
||||||
|
targetrow = [0] * width
|
||||||
|
|
||||||
|
for i, v in enumerate(row):
|
||||||
|
# `it` will be the index of the chosen target colour;
|
||||||
|
it = bisect_left(choosecode, v)
|
||||||
|
targetrow[i] = it
|
||||||
|
t = outcode[it]
|
||||||
|
# err is the error that needs distributing.
|
||||||
|
err = v - t
|
||||||
|
|
||||||
|
# Sierra "Filter Lite" distributes * 2
|
||||||
|
# as per this diagram. 1 1
|
||||||
|
ef = err * 0.5
|
||||||
|
# :todo: consider making rows one wider at each end and
|
||||||
|
# removing "if"s
|
||||||
|
if i + 1 < width:
|
||||||
|
row[i + 1] += ef
|
||||||
|
ef *= 0.5
|
||||||
|
ed[i] = ef
|
||||||
|
if i:
|
||||||
|
ed[i - 1] += ef
|
||||||
|
|
||||||
|
if flipped:
|
||||||
|
ed = ed[::-1]
|
||||||
|
targetrow = targetrow[::-1]
|
||||||
|
yield targetrow
|
||||||
|
flipped = not flipped
|
||||||
|
|
||||||
|
|
||||||
|
WARMUP_ROWS = 32
|
||||||
|
|
||||||
|
|
||||||
|
def repeat_header(rows):
|
||||||
|
"""Repeat the first row, to "warm up" the error register."""
|
||||||
|
for row in rows:
|
||||||
|
yield row
|
||||||
|
for _ in range(WARMUP_ROWS):
|
||||||
|
yield row
|
||||||
|
break
|
||||||
|
yield from rows
|
||||||
|
|
||||||
|
|
||||||
|
def remove_header(rows):
|
||||||
|
"""Remove the same number of rows that repeat_header added."""
|
||||||
|
|
||||||
|
for _ in range(WARMUP_ROWS):
|
||||||
|
next(rows)
|
||||||
|
yield from rows
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# https://docs.python.org/3.5/library/argparse.html
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
if argv is None:
|
||||||
|
argv = sys.argv
|
||||||
|
|
||||||
|
progname, *args = argv
|
||||||
|
|
||||||
|
parser.add_argument("--bitdepth", type=int, default=1, help="bitdepth of output")
|
||||||
|
parser.add_argument(
|
||||||
|
"--cutoff",
|
||||||
|
type=float,
|
||||||
|
default=0.5,
|
||||||
|
help="cutoff to select adjacent output values",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--defaultgamma",
|
||||||
|
type=float,
|
||||||
|
default=1.0,
|
||||||
|
help="gamma value to use when no gamma in input",
|
||||||
|
)
|
||||||
|
parser.add_argument("--linear", action="store_true", help="force linear input")
|
||||||
|
parser.add_argument(
|
||||||
|
"--targetgamma",
|
||||||
|
type=float,
|
||||||
|
help="gamma to use in output (target), defaults to input gamma",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"input", nargs="?", default="-", type=png.cli_open, metavar="PNG"
|
||||||
|
)
|
||||||
|
|
||||||
|
ns = parser.parse_args(args)
|
||||||
|
|
||||||
|
return dither(png.binary_stdout(), **vars(ns))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
275
env/bin/priforgepng
vendored
Executable file
275
env/bin/priforgepng
vendored
Executable file
|
|
@ -0,0 +1,275 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# priforgepng
|
||||||
|
|
||||||
|
"""Forge PNG image from raw computation."""
|
||||||
|
|
||||||
|
from array import array
|
||||||
|
from fractions import Fraction
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import png
|
||||||
|
|
||||||
|
|
||||||
|
def gen_glr(x):
|
||||||
|
"""Gradient Left to Right"""
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
def gen_grl(x):
|
||||||
|
"""Gradient Right to Left"""
|
||||||
|
return 1 - x
|
||||||
|
|
||||||
|
|
||||||
|
def gen_gtb(x, y):
|
||||||
|
"""Gradient Top to Bottom"""
|
||||||
|
return y
|
||||||
|
|
||||||
|
|
||||||
|
def gen_gbt(x, y):
|
||||||
|
"""Gradient Bottom to Top"""
|
||||||
|
return 1.0 - y
|
||||||
|
|
||||||
|
|
||||||
|
def gen_rtl(x, y):
|
||||||
|
"""Radial gradient, centred at Top-Left"""
|
||||||
|
return max(1 - (float(x) ** 2 + float(y) ** 2) ** 0.5, 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_rctr(x, y):
|
||||||
|
"""Radial gradient, centred at Centre"""
|
||||||
|
return gen_rtl(float(x) - 0.5, float(y) - 0.5)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_rtr(x, y):
|
||||||
|
"""Radial gradient, centred at Top-Right"""
|
||||||
|
return gen_rtl(1.0 - float(x), y)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_rbl(x, y):
|
||||||
|
"""Radial gradient, centred at Bottom-Left"""
|
||||||
|
return gen_rtl(x, 1.0 - float(y))
|
||||||
|
|
||||||
|
|
||||||
|
def gen_rbr(x, y):
|
||||||
|
"""Radial gradient, centred at Bottom-Right"""
|
||||||
|
return gen_rtl(1.0 - float(x), 1.0 - float(y))
|
||||||
|
|
||||||
|
|
||||||
|
def stripe(x, n):
|
||||||
|
return int(x * n) & 1
|
||||||
|
|
||||||
|
|
||||||
|
def gen_vs2(x):
|
||||||
|
"""2 Vertical Stripes"""
|
||||||
|
return stripe(x, 2)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_vs4(x):
|
||||||
|
"""4 Vertical Stripes"""
|
||||||
|
return stripe(x, 4)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_vs10(x):
|
||||||
|
"""10 Vertical Stripes"""
|
||||||
|
return stripe(x, 10)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_hs2(x, y):
|
||||||
|
"""2 Horizontal Stripes"""
|
||||||
|
return stripe(float(y), 2)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_hs4(x, y):
|
||||||
|
"""4 Horizontal Stripes"""
|
||||||
|
return stripe(float(y), 4)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_hs10(x, y):
|
||||||
|
"""10 Horizontal Stripes"""
|
||||||
|
return stripe(float(y), 10)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_slr(x, y):
|
||||||
|
"""10 diagonal stripes, rising from Left to Right"""
|
||||||
|
return stripe(x + y, 10)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_srl(x, y):
|
||||||
|
"""10 diagonal stripes, rising from Right to Left"""
|
||||||
|
return stripe(1 + x - y, 10)
|
||||||
|
|
||||||
|
|
||||||
|
def checker(x, y, n):
|
||||||
|
return stripe(x, n) ^ stripe(y, n)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_ck8(x, y):
|
||||||
|
"""8 by 8 checkerboard"""
|
||||||
|
return checker(x, y, 8)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_ck15(x, y):
|
||||||
|
"""15 by 15 checkerboard"""
|
||||||
|
return checker(x, y, 15)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_zero(x):
|
||||||
|
"""All zero (black)"""
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def gen_one(x):
|
||||||
|
"""All one (white)"""
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def yield_fun_rows(size, bitdepth, pattern):
|
||||||
|
"""
|
||||||
|
Create a single channel (monochrome) test pattern.
|
||||||
|
Yield each row in turn.
|
||||||
|
"""
|
||||||
|
|
||||||
|
width, height = size
|
||||||
|
|
||||||
|
maxval = 2 ** bitdepth - 1
|
||||||
|
if maxval > 255:
|
||||||
|
typecode = "H"
|
||||||
|
else:
|
||||||
|
typecode = "B"
|
||||||
|
pfun = pattern_function(pattern)
|
||||||
|
|
||||||
|
# The coordinates are an integer + 0.5,
|
||||||
|
# effectively sampling each pixel at its centre.
|
||||||
|
# This is morally better, and produces all 256 sample values
|
||||||
|
# in a 256-pixel wide gradient.
|
||||||
|
|
||||||
|
# We make a list of x coordinates here and re-use it,
|
||||||
|
# because Fraction instances are slow to allocate.
|
||||||
|
xs = [Fraction(x, 2 * width) for x in range(1, 2 * width, 2)]
|
||||||
|
|
||||||
|
# The general case is a function in x and y,
|
||||||
|
# but if the function only takes an x argument,
|
||||||
|
# it's handled in a special case that is a lot faster.
|
||||||
|
if n_args(pfun) == 2:
|
||||||
|
for y in range(height):
|
||||||
|
a = array(typecode)
|
||||||
|
fy = Fraction(Fraction(y + 0.5), height)
|
||||||
|
for fx in xs:
|
||||||
|
a.append(int(round(maxval * pfun(fx, fy))))
|
||||||
|
yield a
|
||||||
|
return
|
||||||
|
|
||||||
|
# For functions in x only, it's a _lot_ faster
|
||||||
|
# to generate a single row and repeatedly yield it
|
||||||
|
a = array(typecode)
|
||||||
|
for fx in xs:
|
||||||
|
a.append(int(round(maxval * pfun(x=fx))))
|
||||||
|
for y in range(height):
|
||||||
|
yield a
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def generate(args):
|
||||||
|
"""
|
||||||
|
Create a PNG test image and write the file to stdout.
|
||||||
|
|
||||||
|
`args` should be an argparse Namespace instance or similar.
|
||||||
|
"""
|
||||||
|
|
||||||
|
size = args.size
|
||||||
|
bitdepth = args.depth
|
||||||
|
|
||||||
|
out = png.binary_stdout()
|
||||||
|
|
||||||
|
for pattern in args.pattern:
|
||||||
|
rows = yield_fun_rows(size, bitdepth, pattern)
|
||||||
|
writer = png.Writer(
|
||||||
|
size[0], size[1], bitdepth=bitdepth, greyscale=True, alpha=False
|
||||||
|
)
|
||||||
|
writer.write(out, rows)
|
||||||
|
|
||||||
|
|
||||||
|
def n_args(fun):
|
||||||
|
"""Number of arguments in fun's argument list."""
|
||||||
|
return fun.__code__.co_argcount
|
||||||
|
|
||||||
|
|
||||||
|
def pattern_function(pattern):
|
||||||
|
"""From `pattern`, a string,
|
||||||
|
return the function for that pattern.
|
||||||
|
"""
|
||||||
|
|
||||||
|
lpat = pattern.lower()
|
||||||
|
for name, fun in globals().items():
|
||||||
|
parts = name.split("_")
|
||||||
|
if parts[0] != "gen":
|
||||||
|
continue
|
||||||
|
if parts[1] == lpat:
|
||||||
|
return fun
|
||||||
|
|
||||||
|
|
||||||
|
def patterns():
|
||||||
|
"""
|
||||||
|
List the patterns.
|
||||||
|
"""
|
||||||
|
|
||||||
|
for name, fun in globals().items():
|
||||||
|
parts = name.split("_")
|
||||||
|
if parts[0] == "gen":
|
||||||
|
yield parts[1], fun.__doc__
|
||||||
|
|
||||||
|
|
||||||
|
def dimensions(s):
|
||||||
|
"""
|
||||||
|
Typecheck the --size option, which should be
|
||||||
|
one or two comma separated numbers.
|
||||||
|
Example: "64,40".
|
||||||
|
"""
|
||||||
|
|
||||||
|
tupl = re.findall(r"\d+", s)
|
||||||
|
if len(tupl) not in (1, 2):
|
||||||
|
raise ValueError("%r should be width or width,height" % s)
|
||||||
|
if len(tupl) == 1:
|
||||||
|
tupl *= 2
|
||||||
|
assert len(tupl) == 2
|
||||||
|
return list(map(int, tupl))
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
if argv is None:
|
||||||
|
argv = sys.argv
|
||||||
|
parser = argparse.ArgumentParser(description="Forge greyscale PNG patterns")
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-l", "--list", action="store_true", help="print list of patterns and exit"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-d", "--depth", default=8, type=int, metavar="N", help="N bits per pixel"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-s",
|
||||||
|
"--size",
|
||||||
|
default=[256, 256],
|
||||||
|
type=dimensions,
|
||||||
|
metavar="w[,h]",
|
||||||
|
help="width and height of the image in pixels",
|
||||||
|
)
|
||||||
|
parser.add_argument("pattern", nargs="*", help="name of pattern")
|
||||||
|
|
||||||
|
args = parser.parse_args(argv[1:])
|
||||||
|
|
||||||
|
if args.list:
|
||||||
|
for name, doc in sorted(patterns()):
|
||||||
|
print(name, doc, sep="\t")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not args.pattern:
|
||||||
|
parser.error("--list or pattern is required")
|
||||||
|
return generate(args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
72
env/bin/prigreypng
vendored
Executable file
72
env/bin/prigreypng
vendored
Executable file
|
|
@ -0,0 +1,72 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
|
||||||
|
# prigreypng
|
||||||
|
|
||||||
|
# Convert image to grey (L, or LA), but only if that involves no colour change.
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import array
|
||||||
|
|
||||||
|
|
||||||
|
import png
|
||||||
|
|
||||||
|
|
||||||
|
def as_grey(out, inp):
|
||||||
|
"""
|
||||||
|
Convert image to greyscale, but only when no colour change.
|
||||||
|
This works by using the input G channel (green) as
|
||||||
|
the output L channel (luminance) and
|
||||||
|
checking that every pixel is grey as we go.
|
||||||
|
A non-grey pixel will raise an error.
|
||||||
|
"""
|
||||||
|
|
||||||
|
r = png.Reader(file=inp)
|
||||||
|
_, _, rows, info = r.asDirect()
|
||||||
|
if info["greyscale"]:
|
||||||
|
w = png.Writer(**info)
|
||||||
|
return w.write(out, rows)
|
||||||
|
|
||||||
|
planes = info["planes"]
|
||||||
|
targetplanes = planes - 2
|
||||||
|
alpha = info["alpha"]
|
||||||
|
width, height = info["size"]
|
||||||
|
typecode = "BH"[info["bitdepth"] > 8]
|
||||||
|
|
||||||
|
# Values per target row
|
||||||
|
vpr = width * targetplanes
|
||||||
|
|
||||||
|
def iterasgrey():
|
||||||
|
for i, row in enumerate(rows):
|
||||||
|
row = array.array(typecode, row)
|
||||||
|
targetrow = array.array(typecode, [0] * vpr)
|
||||||
|
# Copy G (and possibly A) channel.
|
||||||
|
green = row[0::planes]
|
||||||
|
if alpha:
|
||||||
|
targetrow[0::2] = green
|
||||||
|
targetrow[1::2] = row[3::4]
|
||||||
|
else:
|
||||||
|
targetrow = green
|
||||||
|
# Check R and B channel match.
|
||||||
|
if green != row[0::planes] or green != row[2::planes]:
|
||||||
|
raise ValueError("Row %i contains non-grey pixel." % i)
|
||||||
|
yield targetrow
|
||||||
|
|
||||||
|
info["greyscale"] = True
|
||||||
|
del info["planes"]
|
||||||
|
w = png.Writer(**info)
|
||||||
|
return w.write(out, iterasgrey())
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"input", nargs="?", default="-", type=png.cli_open, metavar="PNG"
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
return as_grey(png.binary_stdout(), args.input)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.exit(main())
|
||||||
111
env/bin/pripalpng
vendored
Executable file
111
env/bin/pripalpng
vendored
Executable file
|
|
@ -0,0 +1,111 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# pripalpng
|
||||||
|
|
||||||
|
|
||||||
|
"""Convert to Palette PNG (without changing colours)"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import collections
|
||||||
|
|
||||||
|
# https://docs.python.org/2.7/library/io.html
|
||||||
|
import io
|
||||||
|
import string
|
||||||
|
import zlib
|
||||||
|
|
||||||
|
# Local module.
|
||||||
|
import png
|
||||||
|
|
||||||
|
|
||||||
|
def make_inverse_palette(rows, channels):
|
||||||
|
"""
|
||||||
|
The inverse palette maps from tuple to palette index.
|
||||||
|
"""
|
||||||
|
|
||||||
|
palette = {}
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
for pixel in png.group(row, channels):
|
||||||
|
if pixel in palette:
|
||||||
|
continue
|
||||||
|
palette[pixel] = len(palette)
|
||||||
|
return palette
|
||||||
|
|
||||||
|
|
||||||
|
def palette_convert(out, inp, palette_file):
|
||||||
|
"""
|
||||||
|
Convert PNG image in `inp` to use a palette, colour type 3,
|
||||||
|
and write converted image to `out`.
|
||||||
|
|
||||||
|
`palette_file` is a file descriptor for the palette to use.
|
||||||
|
|
||||||
|
If `palette_file` is None, then `inp` is used as the palette.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if palette_file is None:
|
||||||
|
inp, palette_file = palette_file, inp
|
||||||
|
|
||||||
|
reader = png.Reader(file=palette_file)
|
||||||
|
w, h, rows, info = asRGBorA8(reader)
|
||||||
|
channels = info["planes"]
|
||||||
|
if not inp:
|
||||||
|
rows = list(rows)
|
||||||
|
|
||||||
|
palette_map = make_inverse_palette(rows, channels)
|
||||||
|
|
||||||
|
if inp:
|
||||||
|
reader = png.Reader(file=inp)
|
||||||
|
w, h, rows, info = asRGBorA8(reader)
|
||||||
|
channels = info["planes"]
|
||||||
|
|
||||||
|
# Default for colours not in palette is to use last entry.
|
||||||
|
last = len(palette_map) - 1
|
||||||
|
|
||||||
|
def map_pixel(p):
|
||||||
|
return palette_map.get(p, last)
|
||||||
|
|
||||||
|
def convert_rows():
|
||||||
|
for row in rows:
|
||||||
|
yield [map_pixel(p) for p in png.group(row, channels)]
|
||||||
|
|
||||||
|
# Make a palette by sorting the pixels according to their index.
|
||||||
|
palette = sorted(palette_map.keys(), key=palette_map.get)
|
||||||
|
pal_info = dict(size=info["size"], palette=palette)
|
||||||
|
|
||||||
|
w = png.Writer(**pal_info)
|
||||||
|
w.write(out, convert_rows())
|
||||||
|
|
||||||
|
|
||||||
|
def asRGBorA8(reader):
|
||||||
|
"""
|
||||||
|
Return (width, height, rows, info) converting to RGB,
|
||||||
|
or RGBA if original has an alpha channel.
|
||||||
|
"""
|
||||||
|
_, _, _, info = reader.read()
|
||||||
|
if info["alpha"]:
|
||||||
|
return reader.asRGBA8()
|
||||||
|
else:
|
||||||
|
return reader.asRGB8()
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
if argv is None:
|
||||||
|
argv = sys.argv
|
||||||
|
|
||||||
|
argv = argv[1:]
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--palette", type=png.cli_open)
|
||||||
|
parser.add_argument(
|
||||||
|
"input", nargs="?", default="-", type=png.cli_open, metavar="PNG"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
palette_convert(png.binary_stdout(), args.input, args.palette)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
355
env/bin/pripamtopng
vendored
Executable file
355
env/bin/pripamtopng
vendored
Executable file
|
|
@ -0,0 +1,355 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
|
||||||
|
# pripamtopng
|
||||||
|
#
|
||||||
|
# Python Raster Image PAM to PNG
|
||||||
|
|
||||||
|
import array
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import png
|
||||||
|
|
||||||
|
Description = """Convert NetPBM PAM/PNM format files to PNG."""
|
||||||
|
|
||||||
|
|
||||||
|
def read_pam_header(infile):
|
||||||
|
"""
|
||||||
|
Read (the rest of a) PAM header.
|
||||||
|
`infile` should be positioned immediately after the initial 'P7' line
|
||||||
|
(at the beginning of the second line).
|
||||||
|
Returns are as for `read_pnm_header`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Unlike PBM, PGM, and PPM, we can read the header a line at a time.
|
||||||
|
header = dict()
|
||||||
|
while True:
|
||||||
|
line = infile.readline().strip()
|
||||||
|
if line == b"ENDHDR":
|
||||||
|
break
|
||||||
|
if not line:
|
||||||
|
raise EOFError("PAM ended prematurely")
|
||||||
|
if line[0] == b"#":
|
||||||
|
continue
|
||||||
|
line = line.split(None, 1)
|
||||||
|
key = line[0]
|
||||||
|
if key not in header:
|
||||||
|
header[key] = line[1]
|
||||||
|
else:
|
||||||
|
header[key] += b" " + line[1]
|
||||||
|
|
||||||
|
required = [b"WIDTH", b"HEIGHT", b"DEPTH", b"MAXVAL"]
|
||||||
|
required_str = b", ".join(required).decode("ascii")
|
||||||
|
result = []
|
||||||
|
for token in required:
|
||||||
|
if token not in header:
|
||||||
|
raise png.Error("PAM file must specify " + required_str)
|
||||||
|
try:
|
||||||
|
x = int(header[token])
|
||||||
|
except ValueError:
|
||||||
|
raise png.Error(required_str + " must all be valid integers")
|
||||||
|
if x <= 0:
|
||||||
|
raise png.Error(required_str + " must all be positive integers")
|
||||||
|
result.append(x)
|
||||||
|
|
||||||
|
return (b"P7",) + tuple(result)
|
||||||
|
|
||||||
|
|
||||||
|
def read_pnm_header(infile):
|
||||||
|
"""
|
||||||
|
Read a PNM header, returning (format,width,height,depth,maxval).
|
||||||
|
Also reads a PAM header (by using a helper function).
|
||||||
|
`width` and `height` are in pixels.
|
||||||
|
`depth` is the number of channels in the image;
|
||||||
|
for PBM and PGM it is synthesized as 1, for PPM as 3;
|
||||||
|
for PAM images it is read from the header.
|
||||||
|
`maxval` is synthesized (as 1) for PBM images.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Generally, see http://netpbm.sourceforge.net/doc/ppm.html
|
||||||
|
# and http://netpbm.sourceforge.net/doc/pam.html
|
||||||
|
|
||||||
|
# Technically 'P7' must be followed by a newline,
|
||||||
|
# so by using rstrip() we are being liberal in what we accept.
|
||||||
|
# I think this is acceptable.
|
||||||
|
magic = infile.read(3).rstrip()
|
||||||
|
if magic == b"P7":
|
||||||
|
# PAM header parsing is completely different.
|
||||||
|
return read_pam_header(infile)
|
||||||
|
|
||||||
|
# Expected number of tokens in header (3 for P4, 4 for P6)
|
||||||
|
expected = 4
|
||||||
|
pbm = (b"P1", b"P4")
|
||||||
|
if magic in pbm:
|
||||||
|
expected = 3
|
||||||
|
header = [magic]
|
||||||
|
|
||||||
|
# We must read the rest of the header byte by byte because
|
||||||
|
# the final whitespace character may not be a newline.
|
||||||
|
# Of course all PNM files in the wild use a newline at this point,
|
||||||
|
# but we are strong and so we avoid
|
||||||
|
# the temptation to use readline.
|
||||||
|
bs = bytearray()
|
||||||
|
backs = bytearray()
|
||||||
|
|
||||||
|
def next():
|
||||||
|
if backs:
|
||||||
|
c = bytes(backs[0:1])
|
||||||
|
del backs[0]
|
||||||
|
else:
|
||||||
|
c = infile.read(1)
|
||||||
|
if not c:
|
||||||
|
raise png.Error("premature EOF reading PNM header")
|
||||||
|
bs.extend(c)
|
||||||
|
return c
|
||||||
|
|
||||||
|
def backup():
|
||||||
|
"""Push last byte of token onto front of backs."""
|
||||||
|
backs.insert(0, bs[-1])
|
||||||
|
del bs[-1]
|
||||||
|
|
||||||
|
def ignore():
|
||||||
|
del bs[:]
|
||||||
|
|
||||||
|
def tokens():
|
||||||
|
ls = lexInit
|
||||||
|
while True:
|
||||||
|
token, ls = ls()
|
||||||
|
if token:
|
||||||
|
yield token
|
||||||
|
|
||||||
|
def lexInit():
|
||||||
|
c = next()
|
||||||
|
# Skip comments
|
||||||
|
if b"#" <= c <= b"#":
|
||||||
|
while c not in b"\n\r":
|
||||||
|
c = next()
|
||||||
|
ignore()
|
||||||
|
return None, lexInit
|
||||||
|
# Skip whitespace (that precedes a token)
|
||||||
|
if c.isspace():
|
||||||
|
ignore()
|
||||||
|
return None, lexInit
|
||||||
|
if not c.isdigit():
|
||||||
|
raise png.Error("unexpected byte %r found in header" % c)
|
||||||
|
return None, lexNumber
|
||||||
|
|
||||||
|
def lexNumber():
|
||||||
|
# According to the specification it is legal to have comments
|
||||||
|
# that appear in the middle of a token.
|
||||||
|
# I've never seen it; and,
|
||||||
|
# it's a bit awkward to code good lexers in Python (no goto).
|
||||||
|
# So we break on such cases.
|
||||||
|
c = next()
|
||||||
|
while c.isdigit():
|
||||||
|
c = next()
|
||||||
|
backup()
|
||||||
|
token = bs[:]
|
||||||
|
ignore()
|
||||||
|
return token, lexInit
|
||||||
|
|
||||||
|
for token in tokens():
|
||||||
|
# All "tokens" are decimal integers, so convert them here.
|
||||||
|
header.append(int(token))
|
||||||
|
if len(header) == expected:
|
||||||
|
break
|
||||||
|
|
||||||
|
final = next()
|
||||||
|
if not final.isspace():
|
||||||
|
raise png.Error("expected header to end with whitespace, not %r" % final)
|
||||||
|
|
||||||
|
if magic in pbm:
|
||||||
|
# synthesize a MAXVAL
|
||||||
|
header.append(1)
|
||||||
|
depth = (1, 3)[magic == b"P6"]
|
||||||
|
return header[0], header[1], header[2], depth, header[3]
|
||||||
|
|
||||||
|
|
||||||
|
def convert_pnm_plain(w, infile, outfile):
|
||||||
|
"""
|
||||||
|
Convert a plain PNM file containing raw pixel data into
|
||||||
|
a PNG file with the parameters set in the writer object.
|
||||||
|
Works for plain PGM formats.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# See convert_pnm_binary for the corresponding function for
|
||||||
|
# binary PNM formats.
|
||||||
|
|
||||||
|
rows = scan_rows_from_file_plain(infile, w.width, w.height, w.planes)
|
||||||
|
w.write(outfile, rows)
|
||||||
|
|
||||||
|
|
||||||
|
def scan_rows_from_file_plain(infile, width, height, planes):
|
||||||
|
"""
|
||||||
|
Generate a sequence of rows from the input file `infile`.
|
||||||
|
The input file should be in a "Netpbm-like" plain format.
|
||||||
|
The input file should be positioned at the beginning of the
|
||||||
|
first value (that is, immediately after the header).
|
||||||
|
The number of pixels to read is taken from
|
||||||
|
the image dimensions (`width`, `height`, `planes`).
|
||||||
|
|
||||||
|
Each row is yielded as a single sequence of values.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Values per row
|
||||||
|
vpr = width * planes
|
||||||
|
|
||||||
|
values = []
|
||||||
|
rows_output = 0
|
||||||
|
|
||||||
|
# The core problem is that input lines (text lines) may not
|
||||||
|
# correspond with pixel rows. We use two nested loops.
|
||||||
|
# The outer loop reads the input one text line at a time;
|
||||||
|
# this will contain a whole number of values, which are
|
||||||
|
# added to the `values` list.
|
||||||
|
# The inner loop strips the first `vpr` values from the
|
||||||
|
# list, until there aren't enough.
|
||||||
|
# Note we can't tell how many iterations the inner loop will
|
||||||
|
# run for, it could be 0 (if not enough values were read to
|
||||||
|
# make a whole pixel row) or many (if the entire image were
|
||||||
|
# on one input line), or somewhere in between.
|
||||||
|
# In PNM there is in general no requirement to have
|
||||||
|
# correspondence between text lines and pixel rows.
|
||||||
|
|
||||||
|
for inp in infile:
|
||||||
|
values.extend(map(int, inp.split()))
|
||||||
|
while len(values) >= vpr:
|
||||||
|
yield values[:vpr]
|
||||||
|
del values[:vpr]
|
||||||
|
rows_output += 1
|
||||||
|
if rows_output >= height:
|
||||||
|
# Diagnostic here if there are spare values?
|
||||||
|
return
|
||||||
|
# Diagnostic here for early EOF?
|
||||||
|
|
||||||
|
|
||||||
|
def convert_pnm_binary(w, infile, outfile):
|
||||||
|
"""
|
||||||
|
Convert a PNM file containing raw pixel data into
|
||||||
|
a PNG file with the parameters set in the writer object.
|
||||||
|
Works for (binary) PGM, PPM, and PAM formats.
|
||||||
|
"""
|
||||||
|
|
||||||
|
rows = scan_rows_from_file(infile, w.width, w.height, w.planes, w.bitdepth)
|
||||||
|
w.write(outfile, rows)
|
||||||
|
|
||||||
|
|
||||||
|
def scan_rows_from_file(infile, width, height, planes, bitdepth):
|
||||||
|
"""
|
||||||
|
Generate a sequence of rows from the input file `infile`.
|
||||||
|
The input file should be in a "Netpbm-like" binary format.
|
||||||
|
The input file should be positioned at the beginning of the first pixel.
|
||||||
|
The number of pixels to read is taken from
|
||||||
|
the image dimensions (`width`, `height`, `planes`);
|
||||||
|
the number of bytes per value is implied by `bitdepth`.
|
||||||
|
Each row is yielded as a single sequence of values.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Values per row
|
||||||
|
vpr = width * planes
|
||||||
|
# Bytes per row
|
||||||
|
bpr = vpr
|
||||||
|
if bitdepth > 8:
|
||||||
|
assert bitdepth == 16
|
||||||
|
bpr *= 2
|
||||||
|
fmt = ">%dH" % vpr
|
||||||
|
|
||||||
|
def line():
|
||||||
|
return array.array("H", struct.unpack(fmt, infile.read(bpr)))
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def line():
|
||||||
|
return array.array("B", infile.read(bpr))
|
||||||
|
|
||||||
|
for y in range(height):
|
||||||
|
yield line()
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args(args):
|
||||||
|
"""
|
||||||
|
Create a parser and parse the command line arguments.
|
||||||
|
"""
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
|
parser = ArgumentParser(description=Description)
|
||||||
|
version = "%(prog)s " + png.__version__
|
||||||
|
parser.add_argument("--version", action="version", version=version)
|
||||||
|
parser.add_argument(
|
||||||
|
"-c",
|
||||||
|
"--compression",
|
||||||
|
type=int,
|
||||||
|
metavar="level",
|
||||||
|
help="zlib compression level (0-9)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"input",
|
||||||
|
nargs="?",
|
||||||
|
default="-",
|
||||||
|
type=png.cli_open,
|
||||||
|
metavar="PAM/PNM",
|
||||||
|
help="input PAM/PNM file to convert",
|
||||||
|
)
|
||||||
|
args = parser.parse_args(args)
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
if argv is None:
|
||||||
|
argv = sys.argv
|
||||||
|
|
||||||
|
args = parse_args(argv[1:])
|
||||||
|
|
||||||
|
# Prepare input and output files
|
||||||
|
infile = args.input
|
||||||
|
|
||||||
|
# Call after parsing, so that --version and --help work.
|
||||||
|
outfile = png.binary_stdout()
|
||||||
|
|
||||||
|
# Encode PNM to PNG
|
||||||
|
format, width, height, depth, maxval = read_pnm_header(infile)
|
||||||
|
|
||||||
|
ok_formats = (b"P2", b"P5", b"P6", b"P7")
|
||||||
|
if format not in ok_formats:
|
||||||
|
raise NotImplementedError("file format %s not supported" % format)
|
||||||
|
|
||||||
|
# The NetPBM depth (number of channels) completely
|
||||||
|
# determines the PNG format.
|
||||||
|
# Observe:
|
||||||
|
# - L, LA, RGB, RGBA are the 4 modes supported by PNG;
|
||||||
|
# - they correspond to 1, 2, 3, 4 channels respectively.
|
||||||
|
# We use the number of channels in the source image to
|
||||||
|
# determine which one we have.
|
||||||
|
# We ignore the NetPBM image type and the PAM TUPLTYPE.
|
||||||
|
greyscale = depth <= 2
|
||||||
|
pamalpha = depth in (2, 4)
|
||||||
|
supported = [2 ** x - 1 for x in range(1, 17)]
|
||||||
|
try:
|
||||||
|
mi = supported.index(maxval)
|
||||||
|
except ValueError:
|
||||||
|
raise NotImplementedError(
|
||||||
|
"input maxval (%s) not in supported list %s" % (maxval, str(supported))
|
||||||
|
)
|
||||||
|
bitdepth = mi + 1
|
||||||
|
writer = png.Writer(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
greyscale=greyscale,
|
||||||
|
bitdepth=bitdepth,
|
||||||
|
alpha=pamalpha,
|
||||||
|
compression=args.compression,
|
||||||
|
)
|
||||||
|
|
||||||
|
plain = format in (b"P1", b"P2", b"P3")
|
||||||
|
if plain:
|
||||||
|
convert_pnm_plain(writer, infile, outfile)
|
||||||
|
else:
|
||||||
|
convert_pnm_binary(writer, infile, outfile)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
sys.exit(main())
|
||||||
|
except png.Error as e:
|
||||||
|
print(e, file=sys.stderr)
|
||||||
|
sys.exit(99)
|
||||||
540
env/bin/priplan9topng
vendored
Executable file
540
env/bin/priplan9topng
vendored
Executable file
|
|
@ -0,0 +1,540 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
|
||||||
|
# Imported from //depot/prj/plan9topam/master/code/plan9topam.py#4 on
|
||||||
|
# 2009-06-15.
|
||||||
|
|
||||||
|
"""Command line tool to convert from Plan 9 image format to PNG format.
|
||||||
|
|
||||||
|
Plan 9 image format description:
|
||||||
|
https://plan9.io/magic/man2html/6/image
|
||||||
|
|
||||||
|
Where possible this tool will use unbuffered read() calls,
|
||||||
|
so that when finished the file offset is exactly at the end of
|
||||||
|
the image data.
|
||||||
|
This is useful for Plan9 subfont files which place font metric
|
||||||
|
data immediately after the image.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Test materials
|
||||||
|
|
||||||
|
# asset/left.bit is a Plan 9 image file, a leftwards facing Glenda.
|
||||||
|
# Other materials have to be scrounged from the internet.
|
||||||
|
# https://plan9.io/sources/plan9/sys/games/lib/sokoban/images/cargo.bit
|
||||||
|
|
||||||
|
import array
|
||||||
|
import collections
|
||||||
|
import io
|
||||||
|
|
||||||
|
# http://www.python.org/doc/2.3.5/lib/module-itertools.html
|
||||||
|
import itertools
|
||||||
|
import os
|
||||||
|
|
||||||
|
# http://www.python.org/doc/2.3.5/lib/module-re.html
|
||||||
|
import re
|
||||||
|
import struct
|
||||||
|
|
||||||
|
# http://www.python.org/doc/2.3.5/lib/module-sys.html
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# https://docs.python.org/3/library/tarfile.html
|
||||||
|
import tarfile
|
||||||
|
|
||||||
|
|
||||||
|
# https://pypi.org/project/pypng/
|
||||||
|
import png
|
||||||
|
|
||||||
|
# internal
|
||||||
|
import prix
|
||||||
|
|
||||||
|
|
||||||
|
class Error(Exception):
|
||||||
|
"""Some sort of Plan 9 image error."""
|
||||||
|
|
||||||
|
|
||||||
|
def block(s, n):
|
||||||
|
return zip(*[iter(s)] * n)
|
||||||
|
|
||||||
|
|
||||||
|
def plan9_as_image(inp):
|
||||||
|
"""Represent a Plan 9 image file as a png.Image instance, so
|
||||||
|
that it can be written as a PNG file.
|
||||||
|
Works with compressed input files and may work with uncompressed files.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Use inp.raw if available.
|
||||||
|
# This avoids buffering and means that when the image is processed,
|
||||||
|
# the resulting input stream is cued up exactly at the end
|
||||||
|
# of the image.
|
||||||
|
inp = getattr(inp, "raw", inp)
|
||||||
|
|
||||||
|
info, blocks = plan9_open_image(inp)
|
||||||
|
|
||||||
|
rows, infodict = plan9_image_rows(blocks, info)
|
||||||
|
|
||||||
|
return png.Image(rows, infodict)
|
||||||
|
|
||||||
|
|
||||||
|
def plan9_open_image(inp):
|
||||||
|
"""Open a Plan9 image file (`inp` should be an already open
|
||||||
|
file object), and return (`info`, `blocks`) pair.
|
||||||
|
`info` should be a Plan9 5-tuple;
|
||||||
|
`blocks` is the input, and it should yield (`row`, `data`)
|
||||||
|
pairs (see :meth:`pixmeta`).
|
||||||
|
"""
|
||||||
|
|
||||||
|
r = inp.read(11)
|
||||||
|
if r == b"compressed\n":
|
||||||
|
info, blocks = decompress(inp)
|
||||||
|
else:
|
||||||
|
# Since Python 3, there is a good chance that this path
|
||||||
|
# doesn't work.
|
||||||
|
info, blocks = glue(inp, r)
|
||||||
|
|
||||||
|
return info, blocks
|
||||||
|
|
||||||
|
|
||||||
|
def glue(f, r):
|
||||||
|
"""Return (info, stream) pair, given `r` the initial portion of
|
||||||
|
the metadata that has already been read from the stream `f`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
r = r + f.read(60 - len(r))
|
||||||
|
return (meta(r), f)
|
||||||
|
|
||||||
|
|
||||||
|
def meta(r):
|
||||||
|
"""Convert 60 byte bytestring `r`, the metadata from an image file.
|
||||||
|
Returns a 5-tuple (*chan*,*minx*,*miny*,*limx*,*limy*).
|
||||||
|
5-tuples may settle into lists in transit.
|
||||||
|
|
||||||
|
As per https://plan9.io/magic/man2html/6/image the metadata
|
||||||
|
comprises 5 words separated by blanks.
|
||||||
|
As it happens each word starts at an index that is a multiple of 12,
|
||||||
|
but this routine does not care about that.
|
||||||
|
"""
|
||||||
|
|
||||||
|
r = r.split()
|
||||||
|
# :todo: raise FormatError
|
||||||
|
if 5 != len(r):
|
||||||
|
raise Error("Expected 5 space-separated words in metadata")
|
||||||
|
r = [r[0]] + [int(x) for x in r[1:]]
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def bitdepthof(chan):
|
||||||
|
"""Return the bitdepth for a Plan9 pixel format string."""
|
||||||
|
|
||||||
|
maxd = 0
|
||||||
|
for c in re.findall(rb"[a-z]\d*", chan):
|
||||||
|
if c[0] != "x":
|
||||||
|
maxd = max(maxd, int(c[1:]))
|
||||||
|
return maxd
|
||||||
|
|
||||||
|
|
||||||
|
def maxvalof(chan):
|
||||||
|
"""Return the netpbm MAXVAL for a Plan9 pixel format string."""
|
||||||
|
|
||||||
|
bitdepth = bitdepthof(chan)
|
||||||
|
return (2 ** bitdepth) - 1
|
||||||
|
|
||||||
|
|
||||||
|
def plan9_image_rows(blocks, metadata):
|
||||||
|
"""
|
||||||
|
Convert (uncompressed) Plan 9 image file to pair of (*rows*, *info*).
|
||||||
|
This is intended to be used by PyPNG format.
|
||||||
|
*info* is the image info (metadata) returned in a dictionary,
|
||||||
|
*rows* is an iterator that yields each row in
|
||||||
|
boxed row flat pixel format.
|
||||||
|
|
||||||
|
`blocks`, should be an iterator of (`row`, `data`) pairs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
chan, minx, miny, limx, limy = metadata
|
||||||
|
rows = limy - miny
|
||||||
|
width = limx - minx
|
||||||
|
nchans = len(re.findall(b"[a-wyz]", chan))
|
||||||
|
alpha = b"a" in chan
|
||||||
|
# Iverson's convention for the win!
|
||||||
|
ncolour = nchans - alpha
|
||||||
|
greyscale = ncolour == 1
|
||||||
|
bitdepth = bitdepthof(chan)
|
||||||
|
maxval = maxvalof(chan)
|
||||||
|
|
||||||
|
# PNG style info dict.
|
||||||
|
meta = dict(
|
||||||
|
size=(width, rows),
|
||||||
|
bitdepth=bitdepth,
|
||||||
|
greyscale=greyscale,
|
||||||
|
alpha=alpha,
|
||||||
|
planes=nchans,
|
||||||
|
)
|
||||||
|
|
||||||
|
arraycode = "BH"[bitdepth > 8]
|
||||||
|
|
||||||
|
return (
|
||||||
|
map(
|
||||||
|
lambda x: array.array(arraycode, itertools.chain(*x)),
|
||||||
|
block(unpack(blocks, rows, width, chan, maxval), width),
|
||||||
|
),
|
||||||
|
meta,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def unpack(f, rows, width, chan, maxval):
|
||||||
|
"""Unpack `f` into pixels.
|
||||||
|
`chan` describes the pixel format using
|
||||||
|
the Plan9 syntax ("k8", "r8g8b8", and so on).
|
||||||
|
Assumes the pixel format has a total channel bit depth
|
||||||
|
that is either a multiple or a divisor of 8
|
||||||
|
(the Plan9 image specification requires this).
|
||||||
|
`f` should be an iterator that returns blocks of input such that
|
||||||
|
each block contains a whole number of pixels.
|
||||||
|
The return value is an iterator that yields each pixel as an n-tuple.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def mask(w):
|
||||||
|
"""An integer, to be used as a mask, with bottom `w` bits set to 1."""
|
||||||
|
|
||||||
|
return (1 << w) - 1
|
||||||
|
|
||||||
|
def deblock(f, depth, width):
|
||||||
|
"""A "packer" used to convert multiple bytes into single pixels.
|
||||||
|
`depth` is the pixel depth in bits (>= 8), `width` is the row width in
|
||||||
|
pixels.
|
||||||
|
"""
|
||||||
|
|
||||||
|
w = depth // 8
|
||||||
|
i = 0
|
||||||
|
for block in f:
|
||||||
|
for i in range(len(block) // w):
|
||||||
|
p = block[w * i : w * (i + 1)]
|
||||||
|
i += w
|
||||||
|
# Convert little-endian p to integer x
|
||||||
|
x = 0
|
||||||
|
s = 1 # scale
|
||||||
|
for j in p:
|
||||||
|
x += s * j
|
||||||
|
s <<= 8
|
||||||
|
yield x
|
||||||
|
|
||||||
|
def bitfunge(f, depth, width):
|
||||||
|
"""A "packer" used to convert single bytes into multiple pixels.
|
||||||
|
Depth is the pixel depth (< 8), width is the row width in pixels.
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert 8 / depth == 8 // depth
|
||||||
|
|
||||||
|
for block in f:
|
||||||
|
col = 0
|
||||||
|
for x in block:
|
||||||
|
for j in range(8 // depth):
|
||||||
|
yield x >> (8 - depth)
|
||||||
|
col += 1
|
||||||
|
if col == width:
|
||||||
|
# A row-end forces a new byte even if
|
||||||
|
# we haven't consumed all of the current byte.
|
||||||
|
# Effectively rows are bit-padded to make
|
||||||
|
# a whole number of bytes.
|
||||||
|
col = 0
|
||||||
|
break
|
||||||
|
x <<= depth
|
||||||
|
|
||||||
|
# number of bits in each channel
|
||||||
|
bits = [int(d) for d in re.findall(rb"\d+", chan)]
|
||||||
|
# colr of each channel
|
||||||
|
# (r, g, b, k for actual colours, and
|
||||||
|
# a, m, x for alpha, map-index, and unused)
|
||||||
|
colr = re.findall(b"[a-z]", chan)
|
||||||
|
|
||||||
|
depth = sum(bits)
|
||||||
|
|
||||||
|
# Select a "packer" that either:
|
||||||
|
# - gathers multiple bytes into a single pixel (for depth >= 8); or,
|
||||||
|
# - splits bytes into several pixels (for depth < 8).
|
||||||
|
if depth >= 8:
|
||||||
|
assert depth % 8 == 0
|
||||||
|
packer = deblock
|
||||||
|
else:
|
||||||
|
assert 8 % depth == 0
|
||||||
|
packer = bitfunge
|
||||||
|
|
||||||
|
for x in packer(f, depth, width):
|
||||||
|
# x is the pixel as an unsigned integer
|
||||||
|
o = []
|
||||||
|
# This is a bit yucky.
|
||||||
|
# Extract each channel from the _most_ significant part of x.
|
||||||
|
for b, col in zip(bits, colr):
|
||||||
|
v = (x >> (depth - b)) & mask(b)
|
||||||
|
x <<= b
|
||||||
|
if col != "x":
|
||||||
|
# scale to maxval
|
||||||
|
v = v * float(maxval) / mask(b)
|
||||||
|
v = int(v + 0.5)
|
||||||
|
o.append(v)
|
||||||
|
yield o
|
||||||
|
|
||||||
|
|
||||||
|
def decompress(f):
|
||||||
|
"""Decompress a Plan 9 image file.
|
||||||
|
The input `f` should be a binary file object that
|
||||||
|
is already cued past the initial 'compressed\n' string.
|
||||||
|
The return result is (`info`, `blocks`);
|
||||||
|
`info` is a 5-tuple of the Plan 9 image metadata;
|
||||||
|
`blocks` is an iterator that yields a (row, data) pair
|
||||||
|
for each block of data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
r = meta(f.read(60))
|
||||||
|
return r, decomprest(f, r[4])
|
||||||
|
|
||||||
|
|
||||||
|
def decomprest(f, rows):
|
||||||
|
"""Iterator that decompresses the rest of a file once the metadata
|
||||||
|
have been consumed."""
|
||||||
|
|
||||||
|
row = 0
|
||||||
|
while row < rows:
|
||||||
|
row, o = deblock(f)
|
||||||
|
yield o
|
||||||
|
|
||||||
|
|
||||||
|
def deblock(f):
|
||||||
|
"""Decompress a single block from a compressed Plan 9 image file.
|
||||||
|
Each block starts with 2 decimal strings of 12 bytes each.
|
||||||
|
Yields a sequence of (row, data) pairs where
|
||||||
|
`row` is the total number of rows processed
|
||||||
|
(according to the file format) and
|
||||||
|
`data` is the decompressed data for this block.
|
||||||
|
"""
|
||||||
|
|
||||||
|
row = int(f.read(12))
|
||||||
|
size = int(f.read(12))
|
||||||
|
if not (0 <= size <= 6000):
|
||||||
|
raise Error("block has invalid size; not a Plan 9 image file?")
|
||||||
|
|
||||||
|
# Since each block is at most 6000 bytes we may as well read it all in
|
||||||
|
# one go.
|
||||||
|
d = f.read(size)
|
||||||
|
i = 0
|
||||||
|
o = []
|
||||||
|
|
||||||
|
while i < size:
|
||||||
|
x = d[i]
|
||||||
|
i += 1
|
||||||
|
if x & 0x80:
|
||||||
|
x = (x & 0x7F) + 1
|
||||||
|
lit = d[i : i + x]
|
||||||
|
i += x
|
||||||
|
o.extend(lit)
|
||||||
|
continue
|
||||||
|
# x's high-order bit is 0
|
||||||
|
length = (x >> 2) + 3
|
||||||
|
# Offset is made from bottom 2 bits of x and 8 bits of next byte.
|
||||||
|
# MSByte LSByte
|
||||||
|
# +---------------------+-------------------------+
|
||||||
|
# | - - - - - - | x1 x0 | d7 d6 d5 d4 d3 d2 d1 d0 |
|
||||||
|
# +-----------------------------------------------+
|
||||||
|
# Had to discover by inspection which way round the bits go,
|
||||||
|
# because https://plan9.io/magic/man2html/6/image doesn't say.
|
||||||
|
# that x's 2 bits are most significant.
|
||||||
|
offset = (x & 3) << 8
|
||||||
|
offset |= d[i]
|
||||||
|
i += 1
|
||||||
|
# Note: complement operator neatly maps (0 to 1023) to (-1 to
|
||||||
|
# -1024). Adding len(o) gives a (non-negative) offset into o from
|
||||||
|
# which to start indexing.
|
||||||
|
offset = ~offset + len(o)
|
||||||
|
if offset < 0:
|
||||||
|
raise Error(
|
||||||
|
"byte offset indexes off the begininning of "
|
||||||
|
"the output buffer; not a Plan 9 image file?"
|
||||||
|
)
|
||||||
|
for j in range(length):
|
||||||
|
o.append(o[offset + j])
|
||||||
|
return row, bytes(o)
|
||||||
|
|
||||||
|
|
||||||
|
FontChar = collections.namedtuple("FontChar", "x top bottom left width")
|
||||||
|
|
||||||
|
|
||||||
|
def font_copy(inp, image, out, control):
|
||||||
|
"""
|
||||||
|
Convert a Plan 9 font (`inp`, `image`) to a series of PNG images,
|
||||||
|
and write them out as a tar file to the file object `out`.
|
||||||
|
Write a text control file out to the file object `control`.
|
||||||
|
|
||||||
|
Each valid glyph in the font becomes a single PNG image;
|
||||||
|
the output is a tar file of all the images.
|
||||||
|
|
||||||
|
A Plan 9 font consists of a Plan 9 image immediately
|
||||||
|
followed by font data.
|
||||||
|
The image for the font should be the `image` argument,
|
||||||
|
the file containing the rest of the font data should be the
|
||||||
|
file object `inp` which should be cued up to the start of
|
||||||
|
the font data that immediately follows the image.
|
||||||
|
|
||||||
|
https://plan9.io/magic/man2html/6/font
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The format is a little unusual, and isn't completely
|
||||||
|
# clearly documented.
|
||||||
|
# Each 6-byte structure (see FontChar above) defines
|
||||||
|
# a rectangular region of the image that is used for each
|
||||||
|
# glyph.
|
||||||
|
# The source image region that is used may be strictly
|
||||||
|
# smaller than the rectangle for the target glyph.
|
||||||
|
# This seems like a micro-optimisation.
|
||||||
|
# For each glyph,
|
||||||
|
# rows above `top` and below `bottom` will not be copied
|
||||||
|
# from the source (they can be assumed to be blank).
|
||||||
|
# No space is saved in the source image, since the rows must
|
||||||
|
# be present.
|
||||||
|
# `x` is always non-decreasing, so the glyphs appear strictly
|
||||||
|
# left-to-image in the source image.
|
||||||
|
# The x of the next glyph is used to
|
||||||
|
# infer the width of the source rectangle.
|
||||||
|
# `top` and `bottom` give the y-coordinate of the top- and
|
||||||
|
# bottom- sides of the rectangle in both source and targets.
|
||||||
|
# `left` is the x-coordinate of the left-side of the
|
||||||
|
# rectangle in the target glyph. (equivalently, the amount
|
||||||
|
# of padding that should be added on the left).
|
||||||
|
# `width` is the advance-width of the glyph; by convention
|
||||||
|
# it is 0 for an undefined glyph.
|
||||||
|
|
||||||
|
name = getattr(inp, "name", "*subfont*name*not*supplied*")
|
||||||
|
|
||||||
|
header = inp.read(36)
|
||||||
|
n, height, ascent = [int(x) for x in header.split()]
|
||||||
|
print("baseline", name, ascent, file=control, sep=",")
|
||||||
|
|
||||||
|
chs = []
|
||||||
|
for i in range(n + 1):
|
||||||
|
bs = inp.read(6)
|
||||||
|
ch = FontChar(*struct.unpack("<HBBBB", bs))
|
||||||
|
chs.append(ch)
|
||||||
|
|
||||||
|
tar = tarfile.open(mode="w|", fileobj=out)
|
||||||
|
|
||||||
|
# Start at 0, increment for every image output
|
||||||
|
# (recall that not every input glyph has an output image)
|
||||||
|
output_index = 0
|
||||||
|
for i in range(n):
|
||||||
|
ch = chs[i]
|
||||||
|
if ch.width == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
print("png", "index", output_index, "glyph", name, i, file=control, sep=",")
|
||||||
|
|
||||||
|
info = dict(image.info, size=(ch.width, height))
|
||||||
|
target = new_image(info)
|
||||||
|
|
||||||
|
source_width = chs[i + 1].x - ch.x
|
||||||
|
rect = ((ch.left, ch.top), (ch.left + source_width, ch.bottom))
|
||||||
|
image_draw(target, rect, image, (ch.x, ch.top))
|
||||||
|
|
||||||
|
# :todo: add source, glyph, and baseline data here (as a
|
||||||
|
# private tag?)
|
||||||
|
o = io.BytesIO()
|
||||||
|
target.write(o)
|
||||||
|
binary_size = o.tell()
|
||||||
|
o.seek(0)
|
||||||
|
|
||||||
|
tarinfo = tar.gettarinfo(arcname="%s/glyph%d.png" % (name, i), fileobj=inp)
|
||||||
|
tarinfo.size = binary_size
|
||||||
|
tar.addfile(tarinfo, fileobj=o)
|
||||||
|
|
||||||
|
output_index += 1
|
||||||
|
|
||||||
|
tar.close()
|
||||||
|
|
||||||
|
|
||||||
|
def new_image(info):
|
||||||
|
"""Return a fresh png.Image instance."""
|
||||||
|
|
||||||
|
width, height = info["size"]
|
||||||
|
vpr = width * info["planes"]
|
||||||
|
row = lambda: [0] * vpr
|
||||||
|
rows = [row() for _ in range(height)]
|
||||||
|
return png.Image(rows, info)
|
||||||
|
|
||||||
|
|
||||||
|
def image_draw(target, rect, source, point):
|
||||||
|
"""The point `point` in the source image is aligned with the
|
||||||
|
top-left of rect in the target image, and then the rectangle
|
||||||
|
in target is replaced with the pixels from `source`.
|
||||||
|
|
||||||
|
This routine assumes that both source and target can have
|
||||||
|
their rows objects indexed (not streamed).
|
||||||
|
"""
|
||||||
|
|
||||||
|
# :todo: there is no attempt to do clipping or channel or
|
||||||
|
# colour conversion. But maybe later?
|
||||||
|
|
||||||
|
if target.info["planes"] != source.info["planes"]:
|
||||||
|
raise NotImplementedError(
|
||||||
|
"source and target must have the same number of planes"
|
||||||
|
)
|
||||||
|
|
||||||
|
if target.info["bitdepth"] != source.info["bitdepth"]:
|
||||||
|
raise NotImplementedError("source and target must have the same bitdepth")
|
||||||
|
|
||||||
|
tl, br = rect
|
||||||
|
left, top = tl
|
||||||
|
right, bottom = br
|
||||||
|
height = bottom - top
|
||||||
|
|
||||||
|
planes = source.info["planes"]
|
||||||
|
|
||||||
|
vpr = (right - left) * planes
|
||||||
|
source_left, source_top = point
|
||||||
|
|
||||||
|
source_l = source_left * planes
|
||||||
|
source_r = source_l + vpr
|
||||||
|
|
||||||
|
target_l = left * planes
|
||||||
|
target_r = target_l + vpr
|
||||||
|
|
||||||
|
for y in range(height):
|
||||||
|
row = source.rows[y + source_top]
|
||||||
|
row = row[source_l:source_r]
|
||||||
|
target.rows[top + y][target_l:target_r] = row
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Convert Plan9 image to PNG")
|
||||||
|
parser.add_argument(
|
||||||
|
"input",
|
||||||
|
nargs="?",
|
||||||
|
default="-",
|
||||||
|
type=png.cli_open,
|
||||||
|
metavar="image",
|
||||||
|
help="image file in Plan 9 format",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--control",
|
||||||
|
default=os.path.devnull,
|
||||||
|
type=argparse.FileType("w"),
|
||||||
|
metavar="ControlCSV",
|
||||||
|
help="(when using --font) write a control CSV file to named file",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--font",
|
||||||
|
action="store_true",
|
||||||
|
help="process as Plan 9 subfont: output a tar file of PNGs",
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
image = plan9_as_image(args.input)
|
||||||
|
image.stream()
|
||||||
|
|
||||||
|
if not args.font:
|
||||||
|
image.write(png.binary_stdout())
|
||||||
|
else:
|
||||||
|
font_copy(args.input, image, png.binary_stdout(), args.control)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
33
env/bin/pripnglsch
vendored
Executable file
33
env/bin/pripnglsch
vendored
Executable file
|
|
@ -0,0 +1,33 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# pripnglsch
|
||||||
|
# PNG List Chunks
|
||||||
|
|
||||||
|
import png
|
||||||
|
|
||||||
|
|
||||||
|
def list_chunks(out, inp):
|
||||||
|
r = png.Reader(file=inp)
|
||||||
|
for t, v in r.chunks():
|
||||||
|
add = ""
|
||||||
|
if len(v) <= 28:
|
||||||
|
add = " " + v.hex()
|
||||||
|
else:
|
||||||
|
add = " " + v[:26].hex() + "..."
|
||||||
|
t = t.decode("ascii")
|
||||||
|
print("%s %10d%s" % (t, len(v), add), file=out)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"input", nargs="?", default="-", type=png.cli_open, metavar="PNG"
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
return list_chunks(sys.stdout, args.input)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
101
env/bin/pripngtopam
vendored
Executable file
101
env/bin/pripngtopam
vendored
Executable file
|
|
@ -0,0 +1,101 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
|
||||||
|
import struct
|
||||||
|
|
||||||
|
import png
|
||||||
|
|
||||||
|
|
||||||
|
def write_pnm(file, plain, rows, meta):
|
||||||
|
"""
|
||||||
|
Write a Netpbm PNM (or PAM) file.
|
||||||
|
*file* output file object;
|
||||||
|
*plain* (a bool) true if writing plain format (not possible for PAM);
|
||||||
|
*rows* an iterator for the rows;
|
||||||
|
*meta* the info dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
meta = dict(meta)
|
||||||
|
meta["maxval"] = 2 ** meta["bitdepth"] - 1
|
||||||
|
meta["width"], meta["height"] = meta["size"]
|
||||||
|
|
||||||
|
# Number of planes determines both image formats:
|
||||||
|
# 1 : L to PGM
|
||||||
|
# 2 : LA to PAM
|
||||||
|
# 3 : RGB to PPM
|
||||||
|
# 4 : RGBA to PAM
|
||||||
|
planes = meta["planes"]
|
||||||
|
|
||||||
|
# Assume inputs are from a PNG file.
|
||||||
|
assert planes in (1, 2, 3, 4)
|
||||||
|
if planes in (1, 3):
|
||||||
|
if 1 == planes:
|
||||||
|
# PGM
|
||||||
|
# Even if maxval is 1 we use PGM instead of PBM,
|
||||||
|
# to avoid converting data.
|
||||||
|
magic = "P5"
|
||||||
|
if plain:
|
||||||
|
magic = "P2"
|
||||||
|
else:
|
||||||
|
# PPM
|
||||||
|
magic = "P6"
|
||||||
|
if plain:
|
||||||
|
magic = "P3"
|
||||||
|
header = "{magic} {width:d} {height:d} {maxval:d}\n".format(magic=magic, **meta)
|
||||||
|
if planes in (2, 4):
|
||||||
|
# PAM
|
||||||
|
# See http://netpbm.sourceforge.net/doc/pam.html
|
||||||
|
if plain:
|
||||||
|
raise Exception("PAM (%d-plane) does not support plain format" % planes)
|
||||||
|
if 2 == planes:
|
||||||
|
tupltype = "GRAYSCALE_ALPHA"
|
||||||
|
else:
|
||||||
|
tupltype = "RGB_ALPHA"
|
||||||
|
header = (
|
||||||
|
"P7\nWIDTH {width:d}\nHEIGHT {height:d}\n"
|
||||||
|
"DEPTH {planes:d}\nMAXVAL {maxval:d}\n"
|
||||||
|
"TUPLTYPE {tupltype}\nENDHDR\n".format(tupltype=tupltype, **meta)
|
||||||
|
)
|
||||||
|
file.write(header.encode("ascii"))
|
||||||
|
|
||||||
|
# Values per row
|
||||||
|
vpr = planes * meta["width"]
|
||||||
|
|
||||||
|
if plain:
|
||||||
|
for row in rows:
|
||||||
|
row_b = b" ".join([b"%d" % v for v in row])
|
||||||
|
file.write(row_b)
|
||||||
|
file.write(b"\n")
|
||||||
|
else:
|
||||||
|
# format for struct.pack
|
||||||
|
fmt = ">%d" % vpr
|
||||||
|
if meta["maxval"] > 0xFF:
|
||||||
|
fmt = fmt + "H"
|
||||||
|
else:
|
||||||
|
fmt = fmt + "B"
|
||||||
|
for row in rows:
|
||||||
|
file.write(struct.pack(fmt, *row))
|
||||||
|
|
||||||
|
file.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Convert PNG to PAM")
|
||||||
|
parser.add_argument("--plain", action="store_true")
|
||||||
|
parser.add_argument(
|
||||||
|
"input", nargs="?", default="-", type=png.cli_open, metavar="PNG"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Encode PNG to PNM (or PAM)
|
||||||
|
image = png.Reader(file=args.input)
|
||||||
|
_, _, rows, info = image.asDirect()
|
||||||
|
write_pnm(png.binary_stdout(), args.plain, rows, info)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.exit(main())
|
||||||
71
env/bin/prirowpng
vendored
Executable file
71
env/bin/prirowpng
vendored
Executable file
|
|
@ -0,0 +1,71 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
|
||||||
|
# http://www.python.org/doc/2.4.4/lib/module-itertools.html
|
||||||
|
import itertools
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import png
|
||||||
|
|
||||||
|
Description = """Join PNG images in a row left-to-right."""
|
||||||
|
|
||||||
|
|
||||||
|
class FormatError(Exception):
|
||||||
|
"""
|
||||||
|
Some problem with the image format.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def join_row(out, l):
|
||||||
|
"""
|
||||||
|
Concatenate the list of images.
|
||||||
|
All input images must be same height and
|
||||||
|
have the same number of channels.
|
||||||
|
They are concatenated left-to-right.
|
||||||
|
`out` is the (open file) destination for the output image.
|
||||||
|
`l` should be a list of open files (the input image files).
|
||||||
|
"""
|
||||||
|
|
||||||
|
l = [png.Reader(file=f) for f in l]
|
||||||
|
|
||||||
|
# Ewgh, side effects.
|
||||||
|
for r in l:
|
||||||
|
r.preamble()
|
||||||
|
|
||||||
|
# The reference height; from the first image.
|
||||||
|
height = l[0].height
|
||||||
|
# The total target width
|
||||||
|
width = 0
|
||||||
|
for i,r in enumerate(l):
|
||||||
|
if r.height != height:
|
||||||
|
raise FormatError('Image %d, height %d, does not match %d.' %
|
||||||
|
(i, r.height, height))
|
||||||
|
width += r.width
|
||||||
|
|
||||||
|
# Various bugs here because different numbers of channels and depths go wrong.
|
||||||
|
pixel, info = zip(*[r.asDirect()[2:4] for r in l])
|
||||||
|
tinfo = dict(info[0])
|
||||||
|
del tinfo['size']
|
||||||
|
w = png.Writer(width, height, **tinfo)
|
||||||
|
|
||||||
|
def iter_all_rows():
|
||||||
|
for row in zip(*pixel):
|
||||||
|
# `row` is a sequence that has one row from each input image.
|
||||||
|
# list() is required here to hasten the lazy row building;
|
||||||
|
# not sure if that's a bug in PyPNG or not.
|
||||||
|
yield list(itertools.chain(*row))
|
||||||
|
w.write(out, iter_all_rows())
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description=Description)
|
||||||
|
parser.add_argument(
|
||||||
|
"input", nargs="*", default="-", type=png.cli_open, metavar="PNG"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
return join_row(png.binary_stdout(), args.input)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv)
|
||||||
215
env/bin/priweavepng
vendored
Executable file
215
env/bin/priweavepng
vendored
Executable file
|
|
@ -0,0 +1,215 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
|
||||||
|
# priweavepng
|
||||||
|
# Weave selected channels from input PNG files into
|
||||||
|
# a multi-channel output PNG.
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import re
|
||||||
|
|
||||||
|
from array import array
|
||||||
|
|
||||||
|
import png
|
||||||
|
|
||||||
|
"""
|
||||||
|
priweavepng file1.png [file2.png ...]
|
||||||
|
|
||||||
|
The `priweavepng` tool combines channels from the input images and
|
||||||
|
weaves a selection of those channels into an output image.
|
||||||
|
|
||||||
|
Conceptually an intermediate image is formed consisting of
|
||||||
|
all channels of all input images in the order given on the command line
|
||||||
|
and in the order of each channel in its image.
|
||||||
|
Then from 1 to 4 channels are selected and
|
||||||
|
an image is output with those channels.
|
||||||
|
The limit on the number of selected channels is
|
||||||
|
imposed by the PNG image format.
|
||||||
|
|
||||||
|
The `-c n` option selects channel `n`.
|
||||||
|
Further channels can be selected either by repeating the `-c` option,
|
||||||
|
or using a comma separated list.
|
||||||
|
For example `-c 3,2,1` will select channels 3, 2, and 1 in that order;
|
||||||
|
if the input is an RGB PNG, this will swop the Red and Blue channels.
|
||||||
|
The order is significant, the order in which the options are given is
|
||||||
|
the order of the output channels.
|
||||||
|
It is permissible, and sometimes useful
|
||||||
|
(for example, grey to colour expansion, see below),
|
||||||
|
to repeat the same channel.
|
||||||
|
|
||||||
|
If no `-c` option is used the default is
|
||||||
|
to select all of the input channels, up to the first 4.
|
||||||
|
|
||||||
|
`priweavepng` does not care about the meaning of the channels
|
||||||
|
and treats them as a matrix of values.
|
||||||
|
|
||||||
|
The numer of output channels determines the colour mode of the PNG file:
|
||||||
|
L (1-channel, Grey), LA (2-channel, Grey+Alpha),
|
||||||
|
RGB (3-channel, Red+Green+Blue), RGBA (4-channel, Red+Green+Blue+Alpha).
|
||||||
|
|
||||||
|
The `priweavepng` tool can be used for a variety of
|
||||||
|
channel building, swopping, and extraction effects:
|
||||||
|
|
||||||
|
Combine 3 grayscale images into RGB colour:
|
||||||
|
priweavepng grey1.png grey2.png grey3.png
|
||||||
|
|
||||||
|
Swop Red and Blue channels in colour image:
|
||||||
|
priweavepng -c 3 -c 2 -c 1 rgb.png
|
||||||
|
|
||||||
|
Extract Green channel as a greyscale image:
|
||||||
|
priweavepng -c 2 rgb.png
|
||||||
|
|
||||||
|
Convert a greyscale image to a colour image (all grey):
|
||||||
|
priweavepng -c 1 -c 1 -c 1 grey.png
|
||||||
|
|
||||||
|
Add alpha mask from a separate (greyscale) image:
|
||||||
|
priweavepng rgb.png grey.png
|
||||||
|
|
||||||
|
Extract alpha mask into a separate (greyscale) image:
|
||||||
|
priweavepng -c 4 rgba.png
|
||||||
|
|
||||||
|
Steal alpha mask from second file and add to first.
|
||||||
|
Note that the intermediate image in this example has 7 channels:
|
||||||
|
priweavepng -c 1 -c 2 -c 3 -c 7 rgb.png rgba.png
|
||||||
|
|
||||||
|
Take Green channel from 3 successive colour images to make a new RGB image:
|
||||||
|
priweavepng -c 2 -c 5 -c 8 rgb1.png rgb2.png rgb3.png
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
Image = collections.namedtuple("Image", "rows info")
|
||||||
|
|
||||||
|
# For each channel in the intermediate raster,
|
||||||
|
# model:
|
||||||
|
# - image: the input image (0-based);
|
||||||
|
# - i: the channel index within that image (0-based);
|
||||||
|
# - bitdepth: the bitdepth of this channel.
|
||||||
|
Channel = collections.namedtuple("Channel", "image i bitdepth")
|
||||||
|
|
||||||
|
|
||||||
|
class Error(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def weave(out, args):
|
||||||
|
"""Stack the input PNG files and extract channels
|
||||||
|
into a single output PNG.
|
||||||
|
"""
|
||||||
|
|
||||||
|
paths = args.input
|
||||||
|
|
||||||
|
if len(paths) < 1:
|
||||||
|
raise Error("Required input is missing.")
|
||||||
|
|
||||||
|
# List of Image instances
|
||||||
|
images = []
|
||||||
|
# Channel map. Maps from channel number (starting from 1)
|
||||||
|
# to an (image_index, channel_index) pair.
|
||||||
|
channel_map = dict()
|
||||||
|
channel = 1
|
||||||
|
|
||||||
|
for image_index, path in enumerate(paths):
|
||||||
|
inp = png.cli_open(path)
|
||||||
|
rows, info = png.Reader(file=inp).asDirect()[2:]
|
||||||
|
rows = list(rows)
|
||||||
|
image = Image(rows, info)
|
||||||
|
images.append(image)
|
||||||
|
# A later version of PyPNG may intelligently support
|
||||||
|
# PNG files with heterogenous bitdepths.
|
||||||
|
# For now, assumes bitdepth of all channels in image
|
||||||
|
# is the same.
|
||||||
|
channel_bitdepth = (image.info["bitdepth"],) * image.info["planes"]
|
||||||
|
for i in range(image.info["planes"]):
|
||||||
|
channel_map[channel + i] = Channel(image_index, i, channel_bitdepth[i])
|
||||||
|
channel += image.info["planes"]
|
||||||
|
|
||||||
|
assert channel - 1 == sum(image.info["planes"] for image in images)
|
||||||
|
|
||||||
|
# If no channels, select up to first 4 as default.
|
||||||
|
if not args.channel:
|
||||||
|
args.channel = range(1, channel)[:4]
|
||||||
|
|
||||||
|
out_channels = len(args.channel)
|
||||||
|
if not (0 < out_channels <= 4):
|
||||||
|
raise Error("Too many channels selected (must be 1 to 4)")
|
||||||
|
alpha = out_channels in (2, 4)
|
||||||
|
greyscale = out_channels in (1, 2)
|
||||||
|
|
||||||
|
bitdepth = tuple(image.info["bitdepth"] for image in images)
|
||||||
|
arraytype = "BH"[max(bitdepth) > 8]
|
||||||
|
|
||||||
|
size = [image.info["size"] for image in images]
|
||||||
|
# Currently, fail unless all images same size.
|
||||||
|
if len(set(size)) > 1:
|
||||||
|
raise NotImplementedError("Cannot cope when sizes differ - sorry!")
|
||||||
|
size = size[0]
|
||||||
|
|
||||||
|
# Values per row, of output image
|
||||||
|
vpr = out_channels * size[0]
|
||||||
|
|
||||||
|
def weave_row_iter():
|
||||||
|
"""
|
||||||
|
Yield each woven row in turn.
|
||||||
|
"""
|
||||||
|
# The zip call creates an iterator that yields
|
||||||
|
# a tuple with each element containing the next row
|
||||||
|
# for each of the input images.
|
||||||
|
for row_tuple in zip(*(image.rows for image in images)):
|
||||||
|
# output row
|
||||||
|
row = array(arraytype, [0] * vpr)
|
||||||
|
# for each output channel select correct input channel
|
||||||
|
for out_channel_i, selection in enumerate(args.channel):
|
||||||
|
channel = channel_map[selection]
|
||||||
|
# incoming row (make it an array)
|
||||||
|
irow = array(arraytype, row_tuple[channel.image])
|
||||||
|
n = images[channel.image].info["planes"]
|
||||||
|
row[out_channel_i::out_channels] = irow[channel.i :: n]
|
||||||
|
yield row
|
||||||
|
|
||||||
|
w = png.Writer(
|
||||||
|
size[0],
|
||||||
|
size[1],
|
||||||
|
greyscale=greyscale,
|
||||||
|
alpha=alpha,
|
||||||
|
bitdepth=bitdepth,
|
||||||
|
interlace=args.interlace,
|
||||||
|
)
|
||||||
|
w.write(out, weave_row_iter())
|
||||||
|
|
||||||
|
|
||||||
|
def comma_list(s):
|
||||||
|
"""
|
||||||
|
Type and return a list of integers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return [int(c) for c in re.findall(r"\d+", s)]
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
import argparse
|
||||||
|
import itertools
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if argv is None:
|
||||||
|
argv = sys.argv
|
||||||
|
argv = argv[1:]
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"-c",
|
||||||
|
"--channel",
|
||||||
|
action="append",
|
||||||
|
type=comma_list,
|
||||||
|
help="list of channels to extract",
|
||||||
|
)
|
||||||
|
parser.add_argument("--interlace", action="store_true", help="write interlaced PNG")
|
||||||
|
parser.add_argument("input", nargs="+")
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
if args.channel:
|
||||||
|
args.channel = list(itertools.chain(*args.channel))
|
||||||
|
|
||||||
|
return weave(png.binary_stdout(), args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
8
env/bin/py.test
vendored
Executable file
8
env/bin/py.test
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pytest import console_main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(console_main())
|
||||||
8
env/bin/pybabel
vendored
Executable file
8
env/bin/pybabel
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from babel.messages.frontend import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/pyftmerge
vendored
Executable file
8
env/bin/pyftmerge
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from fontTools.merge import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/pyftsubset
vendored
Executable file
8
env/bin/pyftsubset
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from fontTools.subset import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/pygmentize
vendored
Executable file
8
env/bin/pygmentize
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pygments.cmdline import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
8
env/bin/pypdfium2
vendored
Executable file
8
env/bin/pypdfium2
vendored
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/home/pilot/frappe-bench/env/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pypdfium2.__main__ import cli_main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(cli_main())
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue