Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for STL and other 3D file formats #263

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions internal/magic/geo.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
package magic

import (
"bufio"
"bytes"
"encoding/binary"
"strconv"
"strings"
)

var (
// Mtl matches a Material Library format file by Wavefront Technologies.
// https://www.loc.gov/preservation/digital/formats/fdd/fdd000508.shtml
// https://www.iana.org/assignments/media-types/model/mtl
Mtl = prefix([]byte("newmtl"))
)

// Shp matches a shape format file.
Expand Down Expand Up @@ -53,3 +63,63 @@ func Shp(raw []byte, limit uint32) bool {
func Shx(raw []byte, limit uint32) bool {
return bytes.HasPrefix(raw, []byte{0x00, 0x00, 0x27, 0x0A})
}

// Stl matches a StereoLithography file.
// STL is available in ASCII as well as Binary representations for compact file format.
// https://docs.fileformat.com/cad/stl/
// https://www.iana.org/assignments/media-types/model/stl
func Stl(raw []byte, limit uint32) bool {
// ASCII check.
if bytes.HasPrefix(raw, []byte("solid")) {
// If the full file content was provided, check file last line.
if len(raw) < int(limit) {
return bytes.Contains(lastNonWSLine(raw), []byte("endsolid"))
}
return true
}

// Binary check.
return bytes.HasPrefix(raw, bytes.Repeat([]byte{0x20}, 80))
}

// Obj matches a 3D object model format by Wavefront Technologies.
// https://www.loc.gov/preservation/digital/formats/fdd/fdd000507.shtml
// https://www.iana.org/assignments/media-types/model/obj
func Obj(raw []byte, limit uint32) bool {
s := bufio.NewScanner(bytes.NewReader(raw))
for s.Scan() {
fs := strings.Fields(s.Text())
// Check if match a geometric vertice format "v x y z [w]".
if (len(fs) == 4 || len(fs) == 5) && fs[0] == "v" {
for _, f := range fs[1:] {
if _, err := strconv.ParseFloat(f, 64); err != nil {
return false
}
return true
}
}
}
return false
}

// Ply matches a Polygon File Format or the Stanford Triangle Format file.
// https://www.loc.gov/preservation/digital/formats/fdd/fdd000501.shtml
func Ply(raw []byte, limit uint32) bool {
s := bufio.NewScanner(bytes.NewReader(raw))

// First line must be "ply".
if !s.Scan() {
return false
}
if s.Text() != "ply" {
return false
}

// Second line declares the subtype.
if !s.Scan() {
return false
}
return s.Text() == "format ascii 1.0" ||
s.Text() == "format binary_little_endian 1.0" ||
s.Text() == "format binary_big_endian 1.0"
}
10 changes: 10 additions & 0 deletions internal/magic/magic.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,16 @@ func firstLine(in []byte) []byte {
return in[:lineEnd]
}

func lastNonWSLine(in []byte) []byte {
s := bytes.Split(in, []byte("\n"))
for i := len(s) - 1; i >= 0; i-- {
if len(trimLWS(trimRWS(s[i]))) > 0 {
return s[i]
}
}
return []byte{}
}

func isWS(b byte) bool {
return b == '\t' || b == '\n' || b == '\x0c' || b == '\r' || b == ' '
}
Expand Down
17 changes: 17 additions & 0 deletions internal/magic/magic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,23 @@ func TestDropLastLine(t *testing.T) {
}
}

func TestLastNonWSLine(t *testing.T) {
tests := []struct {
raw []byte
res []byte
}{
{[]byte(""), []byte{}},
{[]byte("\n\n\n"), []byte{}},
{[]byte("a\nb\ncd \n \t\n"), []byte("cd ")},
}
for i, tt := range tests {
got := lastNonWSLine(tt.raw)
if string(got) != string(tt.res) {
t.Errorf("lastNonWSLine %d error: expected %q; got %q", i, tt.res, got)
}
}
}

func BenchmarkSrt(b *testing.B) {
const subtitle = `1
00:02:16,612 --> 00:02:19,376
Expand Down
6 changes: 6 additions & 0 deletions mimetype_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,12 @@ var files = map[string]string{
"mrc.mrc": "application/marc",
"msi.msi": "application/x-ms-installer",
"msg.msg": "application/vnd.ms-outlook",
"mtl.mtl": "model/mtl",
"ndjson.xl.ndjson": "application/x-ndjson",
"ndjson.ndjson": "application/x-ndjson",
"nes.nes": "application/vnd.nintendo.snes.rom",
"elfobject": "application/x-object",
"obj.obj": "model/obj",
"odf.odf": "application/vnd.oasis.opendocument.formula",
"sxc.sxc": "application/vnd.sun.xml.calc",
"odg.odg": "application/vnd.oasis.opendocument.graphics",
Expand All @@ -152,6 +154,7 @@ var files = map[string]string{
"pdf.pdf": "application/pdf",
"php.php": "text/x-php",
"pl.pl": "text/x-perl",
"ply.ply": "text/plain; charset=utf-8",
"png.png": "image/png",
"ppt.ppt": "application/vnd.ms-powerpoint",
"pptx.pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
Expand All @@ -173,6 +176,9 @@ var files = map[string]string{
"shx.shx": "application/octet-stream",
"so.so": "application/x-sharedlib",
"sqlite.sqlite": "application/vnd.sqlite3",
"stl.ascii.stl": "model/stl",
"stl.ascii2.stl": "model/stl",
"stl.binary.stl": "model/stl",
"srt.srt": "application/x-subrip",
// not.srt.txt uses periods instead of commas for the decimal separators of
// the timestamps.
Expand Down
6 changes: 5 additions & 1 deletion supported_mimes.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## 170 Supported MIME types
## 174 Supported MIME types
This file is automatically generated when running tests. Do not edit manually.

Extension | MIME type | Aliases
Expand Down Expand Up @@ -138,6 +138,9 @@ Extension | MIME type | Aliases
**.gbr** | image/x-gimp-gbr | -
**.glb** | model/gltf-binary | -
**.avif** | image/avif | -
**.mtl** | model/mtl | -
**.obj** | model/obj | -
**.stl** | model/stl | -
**.txt** | text/plain | -
**.html** | text/html | -
**.svg** | image/svg+xml | -
Expand Down Expand Up @@ -173,3 +176,4 @@ Extension | MIME type | Aliases
**.ics** | text/calendar | -
**.warc** | application/warc | -
**.vtt** | text/vtt | -
**.ply** | text/plain | application/octet-stream
17 changes: 17 additions & 0 deletions testdata/mtl.mtl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
newmtl flatwhite
Ka 0.5000 0.5000 0.5000
Kd 1.0000 1.0000 1.0000
illum 1

newmtl shinyred
Ka 0.1985 0.0000 0.0000
Kd 0.5921 0.0167 0.0000
Ks 0.5973 0.2083 0.2083
illum 2
Ns 100.2235

newmtl clearblue
Ka 0.0394 0.0394 0.3300
Kd 0.1420 0.1420 0.9500
illum 1
Tr 0.4300
Loading