Base64-Scripts zusammenfassen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
kyou
User
Beiträge: 50
Registriert: Sonntag 7. November 2010, 08:38

Optimal für Data-URL im CSS finde ich diese Schreibweise, allerdings mit etwas längeren Zeilen:

[codebox=css file=Unbenannt.css]background: url("data:type/subtype;base64,\
R0lGODlhRgAoALMAAOnp6UJCQri4uKqqqiYmJtHR0VhYWH19fWtra/T09MXFxd3d\
3ZycnI2NjQAAAP///yH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78i\
IGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxu\
czp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42\
LWMwNjcgNzkuMTU3NzQ3LCAyMDE1LzAzLzMwLTIzOjQwOjQyICAgICAgICAiPiA8\
cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjIt\
cmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4\
bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnht\
cE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJl\
Zj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVm\
IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNSAoV2lu\
ZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzA4NEQyQzAxRUREMTFF\
NkI0QUU5QzQ5Q0I4RkE5MDYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzA4\
NEQyQzExRUREMTFFNkI0QUU5QzQ5Q0I4RkE5MDYiPiA8eG1wTU06RGVyaXZlZEZy\
b20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3MDg0RDJCRTFFREQxMUU2QjRB\
RTlDNDlDQjhGQTkwNiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3MDg0RDJC\
RjFFREQxMUU2QjRBRTlDNDlDQjhGQTkwNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4g\
PC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38\
+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3M\
y8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2c\
m5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1s\
a2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08\
Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0M\
CwoJCAcGBQQDAgEAACH5BAAAAAAALAAAAABGACgAAAT/8MlJq7046827/2AojmRp\
nmiqrmzrvnAsz3Rt33iu71rh/ICV4PcLyACOoEpgrCGVKWZl4FA8EoSDhOogCB6F\
gMHBIBCCgQbBYYA+KYyfYSGSVg6BBD7xACCCAwRgDgdxCgFfAQ5fCFoSb1sBQQ1N\
IENECBIJYg50FT4AoAMGD4ilAxIKgo9JEwRWV50hdlOEE35EST4PA5mmpoO3rRK5\
PwWzlbcEhhJqQaC7vaWJX7yrfcMPrya0EwakZUF7uKEOvL6JqAsBqKxQDW0ll0UP\
cXRYpOs/B7rm0r9E2nEh8kUPkWQ8piU8AWwhiYYOI0qcSLGixYsYM2rcyLGjx48g\
BENeiAAAOw=="
);[/code]

type/subtype ist in diesem Fall image/gif. Firefox zeigt das Bild aber auch so an.
Also mit Zeilenumbruch vor dem eigentlichen Code und einleitendem Backslash. Dann hat man alles schön in einem Block.

Und die Option für das Backslash habe ich jetzt eingefügt. Ist aber unschöner Code.

Code: Alles auswählen

from __future__ import print_function
import argparse
import base64
 
ENCODERS = [
    (base64.standard_b64encode, base64.standard_b64decode),
    (base64.urlsafe_b64encode, base64.urlsafe_b64decode),
]
 
def parse_args(args=None):
    parser = argparse.ArgumentParser(description='Encode and Decode base64.')
   
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument('-e', '--encode', action='store_false', dest='decode')
    group.add_argument('-d', '--decode', action='store_true')
   
    parser.add_argument('--urlsafe', action='store_true', help='Encode urlsafe')
    parser.add_argument('--line-length', help='Defaults to unlimited', default=0, type=int)
    parser.add_argument('--with-backslash', action='store_true')
 
    parser.add_argument('-i', '--infile', help='Defaults to stdin')
    parser.add_argument('-o', '--outfile', help='Defaults to stdout')
    parser.add_argument('args', nargs='*')
   
    return parser.parse_args(args)
 
def insert_newlines(string, every):
    return '\n'.join(string[i:i+every] for i in xrange(0, len(string), every))
    	
def insert_newlines_with_backslash(string, every=64):
    return '\\\n'.join(string[i:i+every] for i in xrange(0, len(string), every))
 
def get_input(opts):
    if opts.infile is None:
        instr = ' '.join(opts.args).encode()
    else:
        with open(opts.infile, 'r' if opts.decode else 'rb') as ifh:
            instr = ifh.read()
    return instr
   
def write_output(opts, outstr):
    if opts.outfile is None:
        print(outstr)
    else:
        with open(opts.outfile, 'wb' if opts.decode else 'w') as ofh:
            ofh.write(outstr)
 
def main():
    opts = parse_args()
    instr = get_input(opts)
    outstr = ENCODERS[opts.urlsafe][opts.decode](instr)
    if opts.line_length > 0 and not opts.decode and not opts.with_backslash:
        outstr = insert_newlines(outstr, opts.line_length)
    if opts.line_length > 0 and not opts.decode and opts.with_backslash:
        outstr = insert_newlines_with_backslash(outstr, opts.line_length)
    write_output(opts, outstr)
 
if __name__ == '__main__':
    main()
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@kyou: statt zweimal die selbe Abfrage zu machen, packt man die with-backslash-Abfrage einfach in die äußere Bedingung:

Code: Alles auswählen

    if opts.line_length > 0 and not opts.decode:
        if opts.with_backslash:
            outstr = insert_newlines_with_backslash(outstr, opts.line_length)
        else:
            outstr = insert_newlines(outstr, opts.line_length)
oder gleich alles in eine Funktion:

Code: Alles auswählen

def insert_newlines(string, every, end='\n'):
    return end.join(string[i:i+every] for i in xrange(0, len(string), every))

[...]

    if opts.line_length > 0 and not opts.decode:
        outstr = insert_newlines(outstr, opts.line_length,
            end='\\\n' if opts.with_backslash else '\n')
kyou
User
Beiträge: 50
Registriert: Sonntag 7. November 2010, 08:38

@ Sirius3

Das sieht doch schon gleich viel sauberer aus. Danke.

Auf der Basis kann ich jetzt auch eine URL-Data-Option hinzufügen, denke ich. Kleines Problem dabei: Ich muss dann einen Platzhalter für "type/subtype" verwenden und später mit Hilfe des Batch-Scripts ersetzen. Denn die URL-Data muss in Anführungszeichen (Zollzeichen) gesetzt werden und das schließende Anführungszeichen muss ohne Zeilenumbruch auf den Base64-Code folgen. Leider habe ich keinen Weg gefunden, wie ich mit dem Batch-Script den letzten Zeilenumbruch in der Output-Datei entfernen kann. Ich weiß nur, dass ich auf folgende Weise verhindern kann, dass Text, den ich in die Output-Datei schreibe, mit einem Zeilenumbruch schließt:

set /p =irgendeinText<NUL>"Output\%%~nxf.txt"

Die Anführungszeichen würde ich nämlich lieber durch das Batch-Script setzen lassen, weil ich dann direkt den richtigen type/subtype in die Output-Datei schreiben kann, ohne diesen Platzhalter ersetzen zu müssen.
kyou
User
Beiträge: 50
Registriert: Sonntag 7. November 2010, 08:38

So, ich habe die Option --data-url jetzt hinzugefügt.

Sie funktioniert mit Absicht nur, wenn entweder die Zeilenlänge nicht begrenzt wird oder in Kombination mit --with-backslash.

Es gibt aber jetzt schon wieder Mehrfachabfragen von --with-backslash und --data-url. Das geht sicher noch sauberer. Aber ich kann mich da nur langsam herantasten.

Code: Alles auswählen

from __future__ import print_function
import argparse
import base64
 
ENCODERS = [
    (base64.standard_b64encode, base64.standard_b64decode),
    (base64.urlsafe_b64encode, base64.urlsafe_b64decode),
]
 
def parse_args(args=None):
    parser = argparse.ArgumentParser(description='Encode and Decode base64.')
   
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument('-e', '--encode', action='store_false', dest='decode')
    group.add_argument('-d', '--decode', action='store_true')
   
    parser.add_argument('--urlsafe', action='store_true', help='Encode urlsafe')
    parser.add_argument('--line-length', help='Defaults to unlimited', default=0, type=int)
    parser.add_argument('--with-backslash', action='store_true')
    parser.add_argument('--data-url', action='store_true')
 
    parser.add_argument('-i', '--infile', help='Defaults to stdin')
    parser.add_argument('-o', '--outfile', help='Defaults to stdout')
    parser.add_argument('args', nargs='*')
   
    return parser.parse_args(args)
 
def insert_newlines(string, every, end='\n'):
    return end.join(string[i:i+every] for i in xrange(0, len(string), every))
 
def get_input(opts):
    if opts.infile is None:
        instr = ' '.join(opts.args).encode()
    else:
        with open(opts.infile, 'r' if opts.decode else 'rb') as ifh:
            instr = ifh.read()
    return instr
   
def write_output(opts, outstr):
    if opts.outfile is None:
        print(outstr)
    else:
        with open(opts.outfile, 'wb' if opts.decode else 'w') as ofh:
            ofh.write(outstr)
 
def main():
    opts = parse_args()
    instr = get_input(opts)
    outstr = ENCODERS[opts.urlsafe][opts.decode](instr)
    if not opts.decode:
        if opts.line_length > 0:
            outstr = insert_newlines(outstr, opts.line_length, end='\\\n' if opts.with_backslash else '\n')
            if opts.data_url and opts.with_backslash:
                outstr = 'url("data:type/subtype;base64,\\\n' + outstr + '")'
        else:
            if opts.data_url:
                outstr = 'url(data:type/subtype;base64,' + outstr + ')'
    write_output(opts, outstr)
 
if __name__ == '__main__':
    main()
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

Das sinnvollste wäre es doch, der -data-url Option gleich den Mime-Type mitzugeben:

Code: Alles auswählen

    parser.add_argument('--data-url', help='Output as url with given mime type')
[...]
    if not opts.decode:
        if opts.line_length > 0:
            outstr = insert_newlines(outstr, opts.line_length,
                end='\\\n' if opts.with_backslash or opts.data_url else '\n')
        if opts.data_url:
            outstr = 'url("data:{mime_type};base64,{newline}{data}")'.format(
                mime_type=opts.data_url,
                new_line='\\\n' if opts.line_length > 0 else '',
                data = outstr)
kyou
User
Beiträge: 50
Registriert: Sonntag 7. November 2010, 08:38

Das sinnvollste wäre
Allerdings. Danke! Baue ich morgen ein. Für heute ist die Konzentration weg und ich muss mich dringend bei irgendeinem ultraharten südkoreanischen Thriller entspannen … :)
kyou
User
Beiträge: 50
Registriert: Sonntag 7. November 2010, 08:38

Code eingefügt und Hilfe etwas überarbeitet. Schön wäre ein Hilfe-Textblock oben im Script, also einer, bei dem man die Hilfe auch ein wenig "formatieren" (Umbrüche) kann. Ich würde da gerne etwas mehr Text reinpacken.

Und die Ausgabe

"positional arguments:
args"

Keine Ahnung, wozu die gut ist.

(Ich sehe gerade, dass man einen Moderator um die Erlaubnis bitten muss, Beiträgen Dateien anhängen zu dürfen.)

Edit:

Eins noch: Eigentlich wäre es praktischer, das Script vor dem Decoden überprüfen zu lassen, ob der Base64-Code urlsafe ist und es dann den entsprechenden Decoder wählen zu lassen.

Code: Alles auswählen

from __future__ import print_function
import argparse
import base64

ENCODERS = [
    (base64.standard_b64encode, base64.standard_b64decode),
    (base64.urlsafe_b64encode, base64.urlsafe_b64decode),
]

def parse_args(args=None):
    parser = argparse.ArgumentParser(description='Encode and Decode base64.')

    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument('-e', '--encode', action='store_false', dest='decode')
    group.add_argument('-d', '--decode', action='store_true')

    parser.add_argument('--urlsafe', action='store_true', help='in conjunction with encode/decode argument')
    parser.add_argument('--line-length', metavar='', help='Defaults to unlimited', default=0, type=int)
    parser.add_argument('--with-backslash', help='Lines end with backslash (in conjunction with line-length)', action='store_true')
    parser.add_argument('--data-url', metavar='', help='Same as above, but output as url data block with given MIME type (or other string)')

    parser.add_argument('-i', '--infile', metavar='', help='Defaults to stdin')
    parser.add_argument('-o', '--outfile', metavar='', help='Defaults to stdout')
    parser.add_argument('args', nargs='*')

    return parser.parse_args(args)

def insert_newlines(string, every, end='\n'):
    return end.join(string[i:i+every] for i in xrange(0, len(string), every))

def get_input(opts):
    if opts.infile is None:
        instr = ' '.join(opts.args).encode()
    else:
        with open(opts.infile, 'r' if opts.decode else 'rb') as ifh:
            instr = ifh.read()
    return instr

def write_output(opts, outstr):
    if opts.outfile is None:
        print(outstr)
    else:
        with open(opts.outfile, 'wb' if opts.decode else 'w') as ofh:
            ofh.write(outstr)

def main():
    opts = parse_args()
    instr = get_input(opts)
    outstr = ENCODERS[opts.urlsafe][opts.decode](instr)
    if not opts.decode:
        if opts.line_length > 0:
            outstr = insert_newlines(outstr, opts.line_length,
            end='\\\n' if opts.with_backslash or opts.data_url else '\n')
        if opts.data_url:
            outstr = 'url("data:{mime_type};base64,{new_line}{data}")'.format(
            mime_type=opts.data_url,
            new_line='\\\n' if opts.line_length > 0 else '',
            data = outstr)
    write_output(opts, outstr)

if __name__ == '__main__':
    main()
kyou
User
Beiträge: 50
Registriert: Sonntag 7. November 2010, 08:38

Welchen Zweck hat eigentlich diese Zeile?:

Code: Alles auswählen

    parser.add_argument('args', nargs='*')
In der Hilfe wird nämlich über "Optional Arguments" Folgendes angezeigt:

Code: Alles auswählen

positional arguments:
  args
Das finde ich überflüssig.

Und "Optional Arguments":

Die Optionen sind ja nicht alle optional. D. h. das Script macht ja nichts, wenn man es ohne bestimmte Optionen startet.

Kann man das nicht aufteilen in (notwendige) Optionen und optionale Optionen?
BlackJack

@kyou: Da steht nicht „Optional“ sondern „positional“. Und diese Argimente sind in der Tat hier komisch und widersprechen auch dem Hilfetext für ``-i``.

Mit „notwendige Optionen“ hast Du recht, die sind in der Tat komisch weil sie dann ja nicht mehr optional sind und damit keine wirklichen Optionen. Das betrifft in der jetzigen Implementierung ``-e`` und ``-d``. Solange man davon nicht eins zur Voreinstellung macht, ist diese Angabe nicht optional.
kyou
User
Beiträge: 50
Registriert: Sonntag 7. November 2010, 08:38

Da steht nicht „Optional“ sondern „positional“.
Ja doch -- unter den positional arguments:

Code: Alles auswählen

usage: b64.py [-h] (-e | -d) [--urlsafe] [--line-length] [--with-backslash]
              [--data-url] [-i] [-o]
              [args [args ...]]

Encode and Decode base64.

positional arguments:
  args

[b]optional arguments:[/b]
  -h, --help        show this help message and exit
  -e, --encode
  -d, --decode
  --urlsafe         in conjunction with encode/decode argument
  --line-length     Defaults to unlimited
  --with-backslash  Lines end with backslash (in conjunction with line-length)
  --data-url        Same as above, but output as url data block with given
                    MIME type (or other string)
  -i , --infile     Defaults to stdin
  -o , --outfile    Defaults to stdout
Obwohl ich nachgelesen habe, verstehe ich nicht, was ein positional Argument ist. Rein vom Begriff her würde ich davon ausgehen, dass es eine Option ist, die an einer bestimmten Stelle angegeben werden muss, wenn man mehrere Optionen angibt, oder eine Art Suboption.

„args“ unter „positional arguments:“ kann ich zwar auch mit metavar='' löschen, die Überschrift „positional arguments:“ selbst aber nicht.

Und wie ich -e/-d, -i (und -o?) in eine Liste notwendiger Argumente verschieben kann, weiß ich auch nicht.

Für die Funktionalität des Scriptes ist das auch nicht wirklich von Bedeutung, sondern nur ein Schönheitsfehler. Das wäre anders, wenn es noch mehr Optionen gäbe. Dann würde man bei einem unstrukturierten Hilfetext leicht den Überblick verlieren.

Im Übrigen formatiert Python die Hilfe ja im Falle dieses Scriptes eigentlich selbstständig.

Also ich bin zufrieden, dass es dieses Script jetzt gibt. Falls jemand Lust hat, den Schönheitsfehler zu beseitigen, dann nur zu.

------------------

By the way, ich überlege gerade, ob ich mir das Buch Einstieg in Python: Ideal für Programmieranfänger geeignet bestellen soll. Irgend ein Python-Buch hätte ich jedenfalls gerne. Und es müsste auf Deutsch sein. Mein Englisch ist nicht gut genug dafür. Aber ich denke, es wird hier mindestens einen Thread geben, in dem Bücher besprochen wurden. Werde also erstmal selbst suchen, bevor ich nach Empfehlungen frage.
BlackJack

@kyou: Ja klar steht da auch etwas von optionalen Argumenten aber Du hast im Text geschrieben das „über "Optional Arguments" Folgendes angezeigt“ wird und dann aber den Abschnitt „positional arguments“ gezeigt. Ich hatte das „über“ nicht ”räumlich” verstanden und darum einen Widerspruch darin gesehen.

Diesen Abschnitt bekommst Du weg in dem Du dieses Argument einfach komplett weg lässt und nicht einfach nur die Metavariable ”unsichtbar” machst. Wie gesagt sind diese zusätzlichen Argumente eine sehr komische API und widersprechen zudem dem Hilfetext von ``-i`` der fälschlicherweise behauptet es würde von der Standardeingabe gelesen falls kein ``-i`` angegeben wird.

Optionen sind die Dinger mit einem oder zwei '-' am Anfang die eigentlich optional sein sollten. Argumente, oder positionale Argumente, sind die Sachen die man ohne einen Namen davor angeben muss. Da bestimmt deren Position die Bedeutung. Wenn man beispielsweise ein Programm zum kopieren von Dateien hat, könnte ein Aufruf beispielsweise so aussehen ``copy source destination``. Quell- und Zieldateiname sind hier keine Optionen sondern Argumente die man zwingend angeben muss. Und die Reihenfolge, also die Position, bestimmt was was ist. Das erste ist die Quelle, das zweite ist das Ziel. Und dann könnte so ein Programm noch Optionen haben. Beispielsweise ``-i`` oder ``--interactive`` was bewirken könnte, dass das Programm interaktiv nachfragt ob eine bereits bestehende Zieldatei überschrieben werden soll oder nicht.
kyou
User
Beiträge: 50
Registriert: Sonntag 7. November 2010, 08:38

@ Sirius3

Read Roberts, einer der Haupt-Entwickler des AFDKO schreibt:
I have no problem with adding the code itself, but it would need to use a non-viral open source license, like Apache 2.0. Parts of the AFDKO are included in proprietary products, like FontLab, and the GNU license would prevent that.
Da von dem ursprünglichen Script kaum noch was übrig geblieben und es jetzt eigentlich deine Arbeit ist: Kannst du es unter die Apache 2.0 Lizenz stellen?

Keine Ahnung, was er mit "non-viral" meint. Damit kennst du dich wahrscheinlich auch besser aus als ich …
kyou
User
Beiträge: 50
Registriert: Sonntag 7. November 2010, 08:38

@ Sirius3

Viral scheint sich in dem Zusammenhang auf ein übermäßiges Copyleft zu beziehen. Die Gnu Public License wurde auch schon als viral bezeichnet. Laut Wikipedia bestätigen die Apache Software Foundation und die Free Software Foundation (FSF) aber, dass die Apache License 2.0 mit der Version 3 der GNU General Public License (GPL) kompatibel ist.

Der Appendix "How to apply the Apache License to your work" der Apache 2.0 Lizenz besagt u. a., dass der Name des Urhebers erwähnt werden muss. Wollte ich auf einem Forum wie diesem hier anonym bleiben, wäre das für mich z. B. ein Problem, wenn mich jemand um diese Lizenz bäte. Ich vermute aber, das der Name des Urheberhebers auch bei anderen Lizenzen genant werden muss. Würde ja sonst irgendwie keinen Sinn machen.

Jedenfalls -- falls du die Lizenz aus irgend einem Grund nicht ändern möchtest, könnt ich das verstehen.
BlackJack

@kyou: Die Kompatibilität geht nur in eine Richtung: Du kannst Code der unter der Apache 2 Lizenz steht, in einem GPL 3 Projekt verwenden und alles unter GPL 3 weitergeben. Umgekehrt geht es aber nicht, eben wegen der ”Viralität” der GPL-Lizenzen.
kyou
User
Beiträge: 50
Registriert: Sonntag 7. November 2010, 08:38

Danke, BlackJack. In dem Fall sollte es also die Apache 2.0 Lizenz oder eine andere Lizenz sein, die richtung Apache 2.0 kompatibel ist.

@ Sirius3

Falls dir das Arbeit machen würde, dich jetzt mit den Lizenzen zu befassen, lass es bleiben. :wink:

Edit:

Übrigens gab es am 24. Mai ein Public Release des AFDKO. Bis zum nächsten Public Release dauert es eh mindestens ein halbes Jahr, wahrscheinlich länger. Und ich glaube, dass es nur sehr wenige User gibt, die sich das AFDKO selbst aus der GitHub-Repository schnüren.
Antworten