Updated eula, new license and updated Hatenatools to the newest version. preparing to add profiles

This commit is contained in:
pbsds committed 2014-02-03 21:40:59 +01:00
1 parent 92177ddfb0
commit 23e198ccf0
22 files changed
+1479 -815

No files matched your search

+578 -421
View File
File diff suppressed because it is too large. Load diff
+166 -36
View File
@@ -1,12 +1,14 @@
#NTFT.py by pbsds
#Free to use as long as credit is given to pbsds / Peder Bergebakken Sundt
#PIL is required to write images to disk
#AGPL3 licensed
#
#Credit:
#PIL is required to read and write images to disk
#
# -The guys behind TiledGGD at https://code.google.com/p/tiledggd/
# This sped up the my work a lot
import sys
#Credits:
#
# -The guys behind TiledGGD. This sped up my work a lit.
# -Jsafive for supplying .ugo files
#
import sys, os
try:
import Image
hasPIL = True
@@ -39,10 +41,28 @@ def DecAsc(dec, length=None, LittleEndian=False):#Converts a decimal into an asc
if LittleEndian: out.reverse()
return "".join(map(chr, out))
def clamp(value, min, max):
if value > max: return max
if value < min: return min
return value
#Class NTFT:
#
# todo: write documentation
# The NTFT image format stores RGB values as 5 bits each: a value between 0 and 31.
# It has 1 bit of transparancy, which means its either vidssible or invisible. No gradients.
#
# How to use:
#
# Converting to NTFT file:
#
# image = ReadImage(input_path)
# NTFT().SetImage(image).WriteFile(output_path)
#
# Reading NTFT file:
#
# ntft = NTFT().ReadFile(input_path, (width, height))
# WriteImage(ntft.Image, output_path)
#
class NTFT:
def __init__(self):
self.Loaded = False
@@ -52,41 +72,89 @@ class NTFT:
f.close()
return ret
def Read(self, data, (w, h)):
if w*h*2 <> len(data):
print "Invalid size"
#the actual stored data is a image with the sizes padded to the nearest power of 2. The image is then clipped out from it.
psize = []
for i in (w, h):
p = 1
while 1<<p < i:
p += 1
psize.append(1<<p)
pw, ph = psize
#check if it fits the file:
if pw*ph*2 <> len(data):
print "Invalid sizes"
return False
#JUST DO IT!
self.Image = [[None for _ in xrange(h)] for _ in xrange(w)]
for y in xrange(h):
for x in xrange(w):
pos = (x + y*w)*2
#print pos
pos = (x + y*pw)*2
byte = AscDec(data[pos:pos+2], True)
byte = AscDec(data[pos:pos+2], True)
#ARGB1555 -> RGBA8:#require true
#ARGB1555 -> RGBA8:
a = (byte >> 15 ) * 0xFF
b = (byte >> 10 & 0x1F) * 0xFF / 0x1F
g = (byte >> 5 & 0x1F) * 0xFF / 0x1F
r = (byte & 0x1F) * 0xFF / 0x1F
# #RGB565 -> RGBA8:
# a = 0xFF
# r = (byte >> 11 & 0x1F) * 0xFF / 0x1F
# g = (byte >> 5 & 0x3F) * 0xFF / 0x3F
# b = (byte & 0x1F) * 0xFF / 0x1F
#print x, y
self.Image[x][y] = (r<<24) | (g<<16) | (b<<8) | a#RGBA8
self.Loaded = True
return self
def WriteFile(self, path):
pass
if self.Loaded:
f = open(path, "wb")
f.write(self.Pack())
f.close()
return True
else:
return False
def Pack(self):
pass
if not self.Loaded:
return False
w = len(self.Image[0])
h = len(self.Image)
#the actual stored data is a image with the sizes padded to the nearest power of 2
psize = []
for i in size:
p = 1
while 1<<p < i:
p += 1
padded_size.append(1<<p)
out = []
for y in xrange(psize[1]):
for x in xrange(psize[0]):
#read
c = self.Image[clamp(x, 0, w)][clamp(y, 0, h)]
r = c >> 24
g = (c >> 16) & 0xFF
b = (c >> 8 ) & 0xFF
a = c & 0xFF
#convert
a = 1 if a >= 0x80 else 0
r = r * 0x1F / 0xFF
g = g * 0x1F / 0xFF
b = b * 0x1F / 0xFF
#store
out.append(DecAsc((a<<15) | (b<<10) | (g<<5) | r, 2, True))
return "".join(out)
def SetImage(self, Image):
self.Image = Image
self.Loaded = True
return self
#Function WriteImage:
#
# Writes a 2D list if uint32 RGBA values as a image files.
# Designed to work with PPM().Thumbnail or PPM().GetFrame(n)
# Writes a 2D list of uint32 RGBA values as a image files.
# Designed to work with NTFT.Image
#
# This function requires the PIl imaging module
def WriteImage(image, outputPath):
@@ -104,8 +172,39 @@ def WriteImage(image, outputPath):
return True
#Function ReadImage:
#
# Returns a 2D list of uint32 RGBA values of the image file.
# This can be passed into NTFT().SetImage()
#
# This function requires the PIl imaging module
def ReadImage(path):
if not hasPIL: return False
image = Image.open(path)
pixeldata = image.getdata()
w, h = image.size
if len(pixeldata[0]) < 4:
def Combine((r, g, b)):
return (r << 24) | (g << 16) | (b << 8) | 0xFF
else:
def Combine((r, g, b, a)):
return (r << 24) | (g << 16) | (b << 8) | a
ret = []
for x in xrange(w):
line = []
for y in xrange(h):
line.append(Combine(pixeldata[y*w + x]))
ret.append(line)
return ret
#testing:
# i = NTFT().ReadFile("NTFTtests/kaeru.ntft", (64, 32))
# i = NTFT().ReadFile("NTFTtests/kaeru.ntft", (36, 30))
# WriteImage(i.Image, "NTFTtests/kaeru.png")
# i = NTFT().ReadFile("NTFTtests/News.ntft", (32, 32))
@@ -114,27 +213,58 @@ def WriteImage(image, outputPath):
# i = NTFT().ReadFile("NTFTtests/Special Room.ntft", (32, 32))
# WriteImage(i.Image, "NTFTtests/Special Room.png")
#i = NTFT()
#i.Loaded = True
#i.Image = ReadImage("NTFTtests/geh.png")
#i.WriteFile("NTFTtests/geh.ntft")
if __name__ == "__main__":
print " == NTFT.py =="
print " == by pbsds =="
print " == v0.05 =="
print " == v0.70 =="
print
if len(sys.argv) < 4:
if len(sys.argv) < 3:
print "Usage:"
print " NTFT.py <input> <output> <width> <height>"
print " NTFT.py <input> [<output> [<width> <height>]]"
print ""
print "The NTFT file contain only the colordata, so it's up to the user to find and"
print "store the resolution of the images."
print "32x32 is the normal size for button icons in UGO files."
print "Can convert a NTFT to PNG or the other way around."
print "if <output> isn't spesified it will be set to <input> with an another extention"
print ""
print "The NTFT file contain only the colordata, so it's up to the user to find or"
print "store the resolution of the image. <width> and <height> is required"
print "to convert a NTFT file to a image."
print "32x32 is the normal resolution for button icons in UGO files."
sys.exit()
input = sys.argv[1]
output = sys.argv[2]
width = int(sys.argv[3])
height = int(sys.argv[4])
Encode = True#if false it'll decode
if input[-4:].lower == "ntft" or len(sys.argv) >= 5:
Encode = False
if len(sys.argv) >= 3:
output = sys.argv[2]
if len(sys.argv) >= 5:
if (not sys.argv[3].isdigit()) or (not sys.argv[4].isdigit()):
print "Invalid sizes"
sys.exit()
width = int(sys.argv[3])
height = int(sys.argv[4])
else:
width, height = None, None
else:
output = ".".join(input.split(".")[:-1]) + (".ntft" if Encode else ".png")
print "Converting..."
WriteImage(NTFT().ReadFile(input, (width, height)).Image, output)
print "Done!"
if Encode:
i = NTFT()
i.Loaded = True
i.Image = ReadImage("NTFTtests/geh.png")
i.WriteFile(output)
else:
WriteImage(NTFT().ReadFile(input, (width, height)).Image, output)
print "Done!"
+44 -44
View File
@@ -1,13 +1,14 @@
#PPM.py by pbsds for python 2.7
#Free to use. Publicated modifications must give credit to pbsds / Peder Bergebakken Sundt
#PIL is required to write frames and thumbnails to disk
#AGPL3 licensed
#
#PIL is required to write images to disk
#
#Credits:
#
# -Steven for most of the documentation on DSiBrew and his frame decoding example on his talkpage
# -Remark for help on the 8x8 tiling on the preview image.
# -Austin Burk and Jsafive for supplying .tmb files
# -Jsafive for supplying .tmb files
#
import sys, wave#needs os and time aswell
try:
import Image
@@ -562,7 +563,7 @@ def WriteImage(image, outputPath):
return True
#this is just for experimenting with sound decoding:
#not even close to being finished
#not even remotely close to being finished
def DecodeSound(outputpath, data):
f = wave.open(outputpath, "wb")
f.setnchannels(1)
@@ -580,35 +581,34 @@ def DecodeSound(outputpath, data):
f.writeframes(DecAsc(i2<<12, 2, True)*4)
f.close()
#for testing during development:
if 1 == 2:
p = PPM()
print "loading ppm..."
p.ReadFile("PPMtests/test.ppm", (1, 1, 1))
print str(p.OriginalAuthorName), str(p.OriginalAuthorID)
print str(p.EditorAuthorName), str(p.EditorAuthorID)
print "Dumping Thumbnail..."
WriteImage(p.Thumbnail, "thumbnail.png")
print "Dumping frames..."
for i in xrange(p.FrameCount):
WriteImage(p.GetFrame(i), "frames/frame %i.png" % i)
print "Dumping raw sounds..."
for i, data in enumerate(p.SoundData):
f = open("sound %i.bin" % i, "wb")
f.write(data)
f.close()
print "Extracting sounds...."
for i, d in enumerate(p.SoundData): DecodeSound("out_%i.wav"%i, d)
#testing:
# p = PPM()
# print "loading ppm..."
# p.ReadFile("PPMtests/test.ppm", (1, 1, 1))
# print str(p.OriginalAuthorName), str(p.OriginalAuthorID)
# print str(p.EditorAuthorName), str(p.EditorAuthorID)
# print "Dumping Thumbnail..."
# WriteImage(p.Thumbnail, "thumbnail.png")
# print "Dumping frames..."
# for i in xrange(p.FrameCount):
# WriteImage(p.GetFrame(i), "frames/frame %i.png" % i)
# print "Dumping raw sounds..."
# for i, data in enumerate(p.SoundData):
# f = open("sound %i.bin" % i, "wb")
# f.write(data)
# f.close()
# print "Extracting sounds...."
# for i, d in enumerate(p.SoundData): DecodeSound("out_%i.wav"%i, d)
if __name__ == '__main__':
print " == PPM.py =="
print " == by pbsds =="
print " == v1.03 =="
print " == v1.05 =="
print
if len(sys.argv) < 3:
@@ -616,16 +616,16 @@ if __name__ == '__main__':
print " PPM.py <Mode> <Input> [<Output>] [<Frame>]"
print ""
print " <Mode>:"
print " 0: Extracts the thumbnail to the file <Output>"
print " 1: Extracts the frame(s) to <Output>"
print " 2: Dumps the raw sound data files to the folder <Output>"
print " 3: Same as mode 2, but will also dump the experimentally decoded"
print " sounds."
print " 4: Prints out the metadata. Can also write it to <output> which also"
print " supports unicode charactes."
print " Mode 0 and 4 supports TMB files aswell"
print " -t: Extracts the thumbnail to the file <Output>"
print " -f: Extracts the frame(s) to <Output>"
print " -s: Dumps the raw sound data files to the folder <Output>"
print " -S: Same as mode 2, but will also dump the experimentally decoded"
print " sounds."
print " -m: Prints out the metadata. Can also write it to <output> which also"
print " supports unicode charactes."
print " Mode -t and -m supports TMB files aswell"
print " <Frame>"
print " Only used in mode 1"
print " Only used in mode -f"
print " Set this to the exact frame you want to extract(starting at 1) and it"
print " will be saved as a file to <Output>."
print " If not specified, it will extract all frames to the folder <Output>"
@@ -634,7 +634,7 @@ if __name__ == '__main__':
import os, time
if sys.argv[1] == "0":
if sys.argv[1] == "-t":
print "Reading the flipnote file...",
if not os.path.isfile(sys.argv[2]):
print "Error!\nSpecified file doesn't exist!"
@@ -649,7 +649,7 @@ if __name__ == '__main__':
print "Dumping the thumbnail...",
WriteImage(flipnote.GetThumbnail(), sys.argv[3])
print "Done!"
elif sys.argv[1] == "1":
elif sys.argv[1] == "-f":
if len(sys.argv) < 4:
print "Error!"
print "<Output> not specified!"
@@ -689,7 +689,7 @@ if __name__ == '__main__':
print "Dumping frame #%i..." % int(sys.argv[4]),
WriteImage(flipnote.GetFrame(int(sys.argv[4])-1), sys.argv[3])
print "Done!"
elif sys.argv[1] in "23":
elif sys.argv[1] in ("-s", "-S"):
if len(sys.argv) < 4:
print "Error!"
print "<Output> not specified!"
@@ -716,7 +716,7 @@ if __name__ == '__main__':
f.close()
print "Done!"
if sys.argv[1] == "3":
if sys.argv[1] == "-S":
print "Dumping the decoded sound data...",
for i, data in enumerate(flipnote.SoundData):
DecodeSound(os.path.join(sys.argv[3], ("BGM decoded.wav", "SFX1 decoded.wav", "SFX2 decoded.wav", "SFX3 decoded.wav")[i]), data)
@@ -728,7 +728,7 @@ if __name__ == '__main__':
f.write("Frame %i:%s%s%s\n" % (i, " SFX1"*s1, " SFX2"*s2, " SFX3"*s3))
f.close()
print "Done!"
elif sys.argv[1] == "4":
elif sys.argv[1] == "-m":
epoch = time.mktime(time.struct_time([2000, 1, 1, 0, 0, 0, 5, 1, -1]))
if not os.path.isfile(sys.argv[2]):
+11 -7
View File
@@ -3,9 +3,9 @@
== aka Peder Bergebakken Sundt ==
--VERSIONS--
PPM.py: v1.03
UGO.py: v0.09
NTFT.py: v0.05
PPM.py: v1.05
UGO.py: v0.92
NTFT.py: v0.70
--DESCRIPTION--
PPM.py can read the metadata, thumbnail, frames and the sound of Flipnote Studio files: .ppm
@@ -20,11 +20,15 @@ Warning, it's still in a very crude state. Error will occour
These are all tested and written in python 2.7
These are all designed to be usable both as python modules and as stand alone applications.
--LICENCE--
Free to use.
They are free to modify as you wish as long you give credit to me, pbsds / Peder Bergebakken Sundt
--DOCUMENTATION--
Documentation on the formats can be found here:
https://github.com/pbsds/hatena-server/wiki
--LICENSE--
This is licensed by AGPL3
See License.txt
--CREDIT--
-Steven for most of the documentation on DSiBrew and his frame decoding example on his talkpage
-Remark for helping me understanding the 8x8 tiling on the preview images.
-Austin Burk and Jsafive for supplying .tmb and .ugo files
-JSAfive for supplying .tmb and .ugo files
+87 -35
View File
@@ -1,6 +1,18 @@
#UGO.py by pbsds
#AGPL3 licensed
#
#This class reads and writes UGO files.
#cal also export and import it in the xml format
#Extended functionality added by PPM.py from http://pbsds.net/projects/ppm
#It can also export and import it in a xml format
#Extended functionality added by PPM.py
#
#Credits:
#
# -Jsafive for supplying .ugo files
#
#Note:
# The current implentation can possibly mess up the files stored in Section #2, but will do for most needs
# Next version will probably bring a big change in the ugoxml format
#
import sys, os
from base64 import b64encode, b64decode
import xml.etree.ElementTree as ET
@@ -10,7 +22,6 @@ try:
HasPPM = True
except ImportError:
HasPPM = False
#helpers:
def AscDec(ascii, LittleEndian=False):#Converts a ascii string into a decimal
@@ -39,7 +50,7 @@ def DecAsc(dec, length=None, LittleEndian=False):#Converts a decimal into an asc
return "".join(map(chr, out))
def zipalign(length, r=4):
return length + (4 - length % r) if length % r else length
def indentXML(elem, level=0):#borrowed from: http://effbot.org/zone/element-lib.htm#prettyprint
def indentXML(elem, level=0):#"borrowed" from: http://effbot.org/zone/element-lib.htm#prettyprint
i = "\n" + level*"\t"
if len(elem):
if not elem.text or not elem.text.strip():
@@ -97,8 +108,13 @@ class UGO:
if Sections >= 2:
ExtraData = data[zipalign(headerlength+self.TableLength) : zipalign(headerlength+self.TableLength)+self.ExtraLength]
#Read data:
#Parse data:
self.Items = []
#todo: implement this:
self.Files = []#[i] = (filename, filecontent)
#see todo.txt
pos = 0#Extra Data position
tmbcount = 1#used if HasPPM is False
ntftcount = 1#used if no label
@@ -120,7 +136,7 @@ class UGO:
self.Items.append(("topscreen text", labels, num))
continue
elif type == 2:#catogories(like "reccomended" and "new flipnotes" and "most popular")
#this one may change greatly depending on what layout is set in type==0
#this one may visually change greatly depending on what layout is set in type==0
link = i[1]
label = b64decode(i[2]).decode("UTF-16LE")
@@ -128,8 +144,12 @@ class UGO:
self.Items.append(("category", link, label, selected))
continue
elif type == 3:#POST button? "Post Flipnote"
pass
elif type == 3:#POST button/link to POST form. "Post Flipnote" uses this
link = i[1]
label = b64decode(i[2]).decode("UTF-16LE")
self.Items.append(("post", link, label))
continue
elif type == 4:#Button
link = i[1]
trait = int(i[2])
@@ -250,9 +270,13 @@ class UGO:
selected = str(1*selected)
TableOfContents.append("\t".join(("2", link, label, selected)))
elif i[0] == "post":#not yet implented
pass
elif i[0] == "button":
elif i[0] == "post":#3
link, label = i[1:]
label = b64encode(label.encode("UTF-16LE"))
TableOfContents.append("\t".join(("3", link, label)))
elif i[0] == "button":#4
trait, label, link, other, file = i[1:]
trait = str(trait)
@@ -297,7 +321,7 @@ class UGO:
for i in self.Items:
if i[0] == "unknown":
elem = ET.SubElement(ugo_xml, "unknown", type=i[1][0])
elem = ET.SubElement(ugo_xml, "raw", type=i[1][0])
for value in i[1][1:]:
ET.SubElement(elem, "value").text = value
continue
@@ -323,7 +347,11 @@ class UGO:
ET.SubElement(elem, "address").text = link
ET.SubElement(elem, "selected").text = str(selected).lower()
elif i[0] == "post":#3
pass#not yet implented/parsed
elem = ET.SubElement(ugo_xml, "post")
link, label = i[1:]
ET.SubElement(elem, "label").text = label
ET.SubElement(elem, "address").text = link
elif i[0] == "button":#4
elem = ET.SubElement(ugo_xml, "button")
trait, label, link, other, file = i[1:]
@@ -361,9 +389,9 @@ class UGO:
Items = []
for elem in ugo_xml:
if elem.tag == "unknown":
if elem.tag == "raw":
if "type" not in elem.attrib:
if not silent: print "Invalid formatting. <unknown> without \"type\" attribute"
if not silent: print "Invalid formatting. <raw> without \"type\" attribute"
return False
values = [elem.attrib["type"]]
for value in elem:
@@ -438,8 +466,27 @@ class UGO:
selected = value.text[0].lower() in "t1"
Items.append(("category", link, label, selected))
elif elem.tag == "post":#3 not yet implented
pass
elif elem.tag == "post":#3
label = None
link = None
for value in elem:
if value.tag == "label":
if isinstance(label, str):
if not silent: print "Invalid formatting. Multible <label> within <post>"
return False
label = value.text if value.text else ""
elif value.tag == "address":
if isinstance(link, str):
if not silent: print "Invalid formatting. Multible <address> within <post>"
return False
link = value.text if value.text else ""
if None in (link, label):
if not silent: print "Invalid formatting. <button> lacks either a <address> or <label>"
return False
Items.append(("post", link, label))
elif elem.tag == "button":#4
trait = None#todo: add names
label = None
@@ -504,12 +551,12 @@ class UGO:
if __name__ == "__main__":
print " == UGO.py =="
print " == by pbsds =="
print " == v0.90 =="
print " == v0.92 =="
print
if len(sys.argv) < 3:
if len(sys.argv) < 2:
print "Usage:"
print " UGO.py <mode> <input> [<output> [<foldername>]]"
print " UGO.py [<mode>] <input> [<output> [<foldername>]]"
print ""
print " <Mode>:"
print " -d: Converts the UGO file in <input> to a UGOXML file with the same"
@@ -519,12 +566,28 @@ if __name__ == "__main__":
print " <foldername> is relative to the XML."
print " -e: Converts the UGOXML file in <input> to a UGO file with the same"
print " name, unless <output> is specified."
print " If mode is not specified, it will try to find out for itself"
sys.exit()
mode = sys.argv[1]
if mode not in ("-d", "-e"):
print "Invalid <mode> given"
sys.exit()
if os.path.exists(mode):#the mode is actually the file
f = open(mode, "rb")
magic = f.read(4)
f.close()
if magic == "UGAR":
mode = "-d"
print "No mode specified. UGO -> UGOXML chosen"
else:
mode = "-e"
print "No mode specified. UGOXML -> UGO chosen"
sys.argv.insert(1, mode)
else:
print "Invalid <mode> given!"
sys.exit()
if mode == "-d":
input = sys.argv[2]
output = sys.argv[3] if len(sys.argv) >= 4 else sys.argv[2]+"xml"
@@ -549,7 +612,7 @@ if __name__ == "__main__":
try:
ugo = UGO().ReadXML(input, False)
except EL.ParseError:
print "Error!\nThe given file is not in the XML format"
print "Error!\nThe given file is not in the XML format!"
ugo = False
if not ugo:
#it prints sufficient errormessages
@@ -559,15 +622,4 @@ if __name__ == "__main__":
print "Writing UGO..."
ugo.WriteFile(output)
print "Done"
print "Done"