diff --git a/CHANGELOG.md b/CHANGELOG.md index 9130fde5..ca07722c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,8 +32,8 @@ because it turns other people's test suites red. coding as well as the bytes, so the time it says is left holds. The frames between changes cost a few bytes each, so an hour at thirty frames a second with a change every second fits in about a megabyte - (1 075 831 B at the smallest), and a `change_interval` as long as the film - or longer keeps one picture throughout (977 434 B for that hour). Set + (1 072 231 B at the smallest), and a `change_interval` as long as the film + or longer keeps one picture throughout (977 374 B for that hour). Set `duration`, `change_interval`, `frame_rate` (whole rates from 1 to 60), `keyframe_interval` - how far apart the frames a player can start from are - `width`, `height` and `quality`. The picture is the gradient with the @@ -55,6 +55,20 @@ because it turns other people's test suites red. AV1 plays in current browsers, and an older player or a pipeline that expects H.264 may refuse it - which is a test worth having. The window lists it under Video. +- **Films: the `mp4` format.** The same films in an MP4 file: the same + settings, the same moving picture and the same manifest keys as `webm`, at + the exact size you ask for, and a WebM and an MP4 made from the same + settings carry the same AV1 frames. The index of the film comes first in + the file, so a browser starts playing before the whole file is in. To write + that index the film is coded twice, the second time from what the first + kept, which costs seconds - an hour of 1920x1080 changing every second is + coded in under one. The padding is free boxes at the end, so every size + from the smallest film up can be made, 5 GB and more included. A frame + between changes costs about 8 bytes, so an hour at thirty frames a second + with a change every second is 938 423 B at the smallest, and 867 623 B + with one picture throughout. The smallest film at the default settings is + 3312 B. A pipeline that expects H.264 in an MP4 may refuse AV1, which is a + test worth having. - **A length of time as a setting.** `duration`, `change_interval` and `keyframe_interval` take `10s`, `1m30s`, `1h`, `500ms` or `59.9s`, to the millisecond. A bare number is refused rather than read as seconds, and so is a length that does not @@ -146,10 +160,10 @@ because it turns other people's test suites red. ### Changed -- **`empty-and-minimal` now includes a film.** The preset builds the smallest - file of every format this build has, so with `formats` left at `all` it - gains a `webm` target, and the recipe `tfg preset eject empty-and-minimal` - prints is a target longer. A manifest from a run of that preset records the +- **`empty-and-minimal` now includes the films.** The preset builds the + smallest file of every format this build has, so with `formats` left at + `all` it gains a `webm` and an `mp4` target, and the recipe + `tfg preset eject empty-and-minimal` prints is two targets longer. A manifest from a run of that preset records the hash of that recipe, so its `recipe_hash` differs from one written before. No generated file changes, and a run that names its `formats` is untouched. - **An empty text box in the window reads "not set"** instead of "worked out diff --git a/README.md b/README.md index 87dbefe9..a5257380 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ **Testing Files Generator** is a tool for QA engineers and developers who need real files to test against - an upload form, a parser, anything that takes a file and -has an opinion about it. You pick one of its 27 formats and the size you want, +has an opinion about it. You pick one of its 28 formats and the size you want, and you get **exactly that**: ask for a 10 MB PDF and you get a PDF that a reader will open, at 10 MB to the byte. Every run also leaves a manifest saying **what your system should do with each file**, which is the part other generators leave @@ -23,7 +23,7 @@ needs it finds out it exists. - **Hit an exact size, to the byte** - ask for 10485761 bytes and get exactly that, never a silently rounded file. -- **Write 27 real formats** - a generated PNG opens in an image viewer, a DOCX +- **Write 28 real formats** - a generated PNG opens in an image viewer, a DOCX opens in Word, a ZIP extracts. Not padded zeros with an extension. - **Say what should happen to each file** - the manifest carries an expected outcome, so your test reads the assertion instead of you writing it out. @@ -167,9 +167,9 @@ owns it, at the exact size you asked for: | ⚙️ **Configuration** | `yaml`, `toml` | | 🗜️ **Archives** | `zip`, `targz` (`.tar.gz`) | | 🔊 **Audio** | `wav` | -| 🎞️ **Video** | `webm` (AV1, no sound) | +| 🎞️ **Video** | `webm`, `mp4` (both AV1, no sound) | -Coming next: `7z`, `mp3`, `mp4`. +Coming next: `7z`, `mp3`. Most of them take settings of their own - image dimensions, JPEG quality, PDF page count, rows and columns in a spreadsheet, what goes inside an archive. See @@ -606,7 +606,7 @@ recipe. `tfg formats ` prints the allowed range or list for each: | `avif`, `jpg`, `jxl` | `width`, `height`, `quality` | | `ico` | `width`, `height`, `embed` | | `wav` | `sample_rate`, `bit_depth`, `channels`, `content` | -| `webm` | `width`, `height`, `duration`, `change_interval`, `frame_rate`, `keyframe_interval`, `quality` | +| `webm`, `mp4` | `width`, `height`, `duration`, `change_interval`, `frame_rate`, `keyframe_interval`, `quality` | | `zip` | `entries`, `entry_format`, `entry_size`, `compression`, `depth`, `directory_entries`, `password`, `encryption` | | `targz` | `entries`, `entry_format`, `entry_size`, `compression`, `depth`, `directory_entries`, `entry_mode`, `entry_owner` | | `docx` | `paragraphs` | diff --git a/internal/format/all/all.go b/internal/format/all/all.go index f65b553d..c96d82b4 100644 --- a/internal/format/all/all.go +++ b/internal/format/all/all.go @@ -20,6 +20,7 @@ import ( _ "github.com/donislawdev/TestingFilesGenerator/internal/format/jxl" _ "github.com/donislawdev/TestingFilesGenerator/internal/format/logfile" _ "github.com/donislawdev/TestingFilesGenerator/internal/format/md" + _ "github.com/donislawdev/TestingFilesGenerator/internal/format/mp4" _ "github.com/donislawdev/TestingFilesGenerator/internal/format/pdf" _ "github.com/donislawdev/TestingFilesGenerator/internal/format/png" _ "github.com/donislawdev/TestingFilesGenerator/internal/format/pptx" diff --git a/internal/format/avif/avif.go b/internal/format/avif/avif.go index 5d37c6d0..6e00a82a 100644 --- a/internal/format/avif/avif.go +++ b/internal/format/avif/avif.go @@ -40,15 +40,14 @@ package avif import ( "context" - "encoding/binary" "fmt" "io" - "math/rand/v2" "strconv" "github.com/donislawdev/TestingFilesGenerator/internal/core" "github.com/donislawdev/TestingFilesGenerator/internal/format" "github.com/donislawdev/TestingFilesGenerator/internal/format/imagedim" + "github.com/donislawdev/TestingFilesGenerator/internal/format/isobmff" ) const ( @@ -56,12 +55,7 @@ const ( // boxHeader is what every box costs before its content: a four byte // length and a four character name. - boxHeader = 8 - - // maxFreePayload keeps a single box inside the four byte length field the - // container uses by default. Anything larger is spread over several boxes, - // which is measured to work rather than assumed. - maxFreePayload = 1<<31 - boxHeader + boxHeader = isobmff.BoxHeader minDimension = 1 maxDimension = 16384 @@ -432,73 +426,5 @@ func (generator) Write(ctx context.Context, w io.Writer, p format.Plan) error { if _, err := w.Write(coded); err != nil { return err } - return writePadding(ctx, w, m.seed, pad) -} - -// writePadding fills the rest of the file with free boxes, without ever -// holding their content. -// -// Several boxes rather than one when the padding is larger than a box length -// can say. The step below keeps the leftover from landing between one and -// seven bytes, which no box could then carry. -func writePadding(ctx context.Context, w io.Writer, seed uint64, total int64) error { - rng := core.NewRand(seed) - buf := make([]byte, 32*1024) - - for total > 0 { - payload := nextPayload(total) - if err := writeBoxHeader(w, payload); err != nil { - return err - } - if err := writeFiller(ctx, w, buf, rng, payload); err != nil { - return err - } - total -= boxHeader + payload - } - return nil -} - -// nextPayload is how much filler the next free box carries. -// -// A box states its length in four bytes, so padding larger than that is spread -// over several boxes. The step down is what keeps the leftover from landing -// between one and seven bytes, which no box could then carry. -func nextPayload(total int64) int64 { - payload := total - boxHeader - if payload <= maxFreePayload { - return payload - } - payload = maxFreePayload - if total-boxHeader-payload < boxHeader { - payload -= boxHeader - } - return payload -} - -func writeBoxHeader(w io.Writer, payload int64) error { - var b [boxHeader]byte - binary.BigEndian.PutUint32(b[:4], uint32(boxHeader+payload)) - copy(b[4:], "free") - _, err := w.Write(b[:]) - return err -} - -func writeFiller(ctx context.Context, w io.Writer, buf []byte, rng *rand.Rand, size int64) error { - for left := size; left > 0; { - n := int64(len(buf)) - if left < n { - n = left - } - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - core.FillRandomLE(buf[:n], rng) - if _, err := w.Write(buf[:n]); err != nil { - return err - } - left -= n - } - return nil + return isobmff.WritePadding(ctx, w, m.seed, pad) } diff --git a/internal/format/isobmff/isobmff.go b/internal/format/isobmff/isobmff.go new file mode 100644 index 00000000..9b60f7aa --- /dev/null +++ b/internal/format/isobmff/isobmff.go @@ -0,0 +1,94 @@ +// Package isobmff is what the formats built of ISO base media boxes share: +// the header every box starts with, and the free boxes their padding is +// carried in. AVIF and JPEG XL each held a copy of the same padding, and MP4 +// would have been the third (docs/MP4-2026-10-07.md section 4). +package isobmff + +import ( + "context" + "encoding/binary" + "io" + "math/rand/v2" + + "github.com/donislawdev/TestingFilesGenerator/internal/core" +) + +// BoxHeader is what every box costs before its content: a four byte length +// and a four character name. +const BoxHeader = 8 + +// maxFreePayload keeps a single box inside the four byte length field the +// container uses by default. Anything larger is spread over several boxes, +// which is measured to work rather than assumed. +const maxFreePayload = 1<<31 - BoxHeader + +// WriteBoxHeader writes the header of a box of kind whose content is payload +// bytes long, in the four byte length every box here uses. +func WriteBoxHeader(w io.Writer, kind string, payload int64) error { + var b [BoxHeader]byte + binary.BigEndian.PutUint32(b[:4], uint32(BoxHeader+payload)) + copy(b[4:], kind) + _, err := w.Write(b[:]) + return err +} + +// WritePadding fills total bytes with free boxes, without ever holding their +// content - bytes drawn from seed, so the same file comes out every time. +// total is at least BoxHeader, because no box is shorter. +// +// Several boxes rather than one when the padding is larger than a box length +// can say. The step below keeps the leftover from landing between one and +// seven bytes, which no box could then carry. +func WritePadding(ctx context.Context, w io.Writer, seed uint64, total int64) error { + rng := core.NewRand(seed) + buf := make([]byte, 32*1024) + + for total > 0 { + payload := nextPayload(total) + if err := WriteBoxHeader(w, "free", payload); err != nil { + return err + } + if err := writeFiller(ctx, w, buf, rng, payload); err != nil { + return err + } + total -= BoxHeader + payload + } + return nil +} + +// nextPayload is how much filler the next free box carries. +// +// A box states its length in four bytes, so padding larger than that is spread +// over several boxes. The step down is what keeps the leftover from landing +// between one and seven bytes, which no box could then carry. +func nextPayload(total int64) int64 { + payload := total - BoxHeader + if payload <= maxFreePayload { + return payload + } + payload = maxFreePayload + if total-BoxHeader-payload < BoxHeader { + payload -= BoxHeader + } + return payload +} + +func writeFiller(ctx context.Context, w io.Writer, buf []byte, rng *rand.Rand, size int64) error { + for left := size; left > 0; { + n := int64(len(buf)) + if left < n { + n = left + } + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + core.FillRandomLE(buf[:n], rng) + if _, err := w.Write(buf[:n]); err != nil { + return err + } + left -= n + } + return nil +} diff --git a/internal/format/jxl/jxl.go b/internal/format/jxl/jxl.go index dd8102f7..e9fb826d 100644 --- a/internal/format/jxl/jxl.go +++ b/internal/format/jxl/jxl.go @@ -54,15 +54,14 @@ package jxl import ( "context" - "encoding/binary" "fmt" "io" - "math/rand/v2" "strconv" "github.com/donislawdev/TestingFilesGenerator/internal/core" "github.com/donislawdev/TestingFilesGenerator/internal/format" "github.com/donislawdev/TestingFilesGenerator/internal/format/imagedim" + "github.com/donislawdev/TestingFilesGenerator/internal/format/isobmff" ) const ( @@ -70,12 +69,7 @@ const ( // boxHeader is what every box costs before its content: a four byte // length and a four character name. - boxHeader = 8 - - // maxFreePayload keeps a single box inside the four byte length field the - // container uses by default. Anything larger is spread over several boxes, - // which is measured to work rather than assumed. - maxFreePayload = 1<<31 - boxHeader + boxHeader = isobmff.BoxHeader // containerOverhead is everything the container costs around the // codestream: the signature box, the file type box, and the header of the @@ -478,79 +472,11 @@ func (generator) Write(ctx context.Context, w io.Writer, p format.Plan) error { if _, err := w.Write(fileType[:]); err != nil { return err } - if err := writeBoxHeader(w, "jxlc", int64(len(coded))); err != nil { + if err := isobmff.WriteBoxHeader(w, "jxlc", int64(len(coded))); err != nil { return err } if _, err := w.Write(coded); err != nil { return err } - return writePadding(ctx, w, m.seed, pad) -} - -// writePadding fills the rest of the file with free boxes, without ever -// holding their content. -// -// Several boxes rather than one when the padding is larger than a box length -// can say. The step below keeps the leftover from landing between one and -// seven bytes, which no box could then carry. -func writePadding(ctx context.Context, w io.Writer, seed uint64, total int64) error { - rng := core.NewRand(seed) - buf := make([]byte, 32*1024) - - for total > 0 { - payload := nextPayload(total) - if err := writeBoxHeader(w, "free", payload); err != nil { - return err - } - if err := writeFiller(ctx, w, buf, rng, payload); err != nil { - return err - } - total -= boxHeader + payload - } - return nil -} - -// nextPayload is how much filler the next free box carries. -// -// A box states its length in four bytes, so padding larger than that is spread -// over several boxes. The step down is what keeps the leftover from landing -// between one and seven bytes, which no box could then carry. -func nextPayload(total int64) int64 { - payload := total - boxHeader - if payload <= maxFreePayload { - return payload - } - payload = maxFreePayload - if total-boxHeader-payload < boxHeader { - payload -= boxHeader - } - return payload -} - -func writeBoxHeader(w io.Writer, kind string, payload int64) error { - var b [boxHeader]byte - binary.BigEndian.PutUint32(b[:4], uint32(boxHeader+payload)) - copy(b[4:], kind) - _, err := w.Write(b[:]) - return err -} - -func writeFiller(ctx context.Context, w io.Writer, buf []byte, rng *rand.Rand, size int64) error { - for left := size; left > 0; { - n := int64(len(buf)) - if left < n { - n = left - } - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - core.FillRandomLE(buf[:n], rng) - if _, err := w.Write(buf[:n]); err != nil { - return err - } - left -= n - } - return nil + return isobmff.WritePadding(ctx, w, m.seed, pad) } diff --git a/internal/format/mp4/layout.go b/internal/format/mp4/layout.go new file mode 100644 index 00000000..2873623a --- /dev/null +++ b/internal/format/mp4/layout.go @@ -0,0 +1,278 @@ +package mp4 + +import ( + "encoding/binary" + "math" + + "github.com/donislawdev/TestingFilesGenerator/internal/format/isobmff" + "github.com/donislawdev/TestingFilesGenerator/internal/format/video" +) + +// fullBoxHeader is a box header with the version and flags a full box adds. +const fullBoxHeader = isobmff.BoxHeader + 4 + +// The sample tables' entries, in bytes. +const ( + syncEntry = 4 // stss: a sample number + chunkRun = 12 // stsc: first chunk, samples per chunk, description + sizeEntry = 4 // stsz + narrowChunk = 4 // stco + wideChunk = 8 // co64 +) + +// layout is a film's file before its pictures are coded: every box of moov +// except the sample tables as bytes, and the lengths of those tables. The +// tables have an entry per frame, key frame or chunk - never anything that +// depends on what a picture codes to - so moov is the same length whatever the +// pictures come to, planning counts it without coding any, and writing puts it +// before them, where a browser reads it first (docs/MP4-2026-10-07.md +// sections 2 and 3). +// +// A chunk opens at every key frame and every change of picture, the places +// WebM opens a cluster, so a chunk carries at most one picture. ISO base media +// lets one chunk hold a whole track, which the probe of 2026-10-06 played, but +// files from a real muxer are cut into chunks, and a test file is meant to +// look like one. +type layout struct { + stream video.Stream + counts [video.SampleKinds]int64 + chunks int64 + // runs is how many entries stsc has: one for each run of chunks that hold + // the same number of samples. + runs int64 + // wide is whether the chunks' places take eight bytes each (co64), and + // bigData whether the media data's length does - each only when a film + // whose pictures take their whole reserve would need it, so a smaller film + // never has a field it could not fill. + wide, bigData bool + + ftyp, mvhd, tkhd, mdhd, hdlr, vmhd, dinf, stsd, stts []byte +} + +func newLayout(s video.Stream) layout { + l := layout{stream: s, counts: s.SampleCounts()} + last := int64(-1) + l.eachChunk(func(_, samples int64) { + l.chunks++ + l.runs += boolCount(samples != last) + last = samples + }) + l.ftyp = fileType() + l.mvhd, l.tkhd, l.mdhd = movieHeader(s), trackHeader(s), mediaHeader(s) + l.hdlr, l.vmhd, l.dinf = handler(), videoHeader(), dataInformation() + l.stsd, l.stts = sampleDescription(s), timeToSample(s) + + content := l.boundContent() + l.bigData = isobmff.BoxHeader+content > math.MaxUint32 + l.wide = l.front()+content > math.MaxUint32 + return l +} + +// eachChunk walks the chunks in order, each by its first frame and how many +// frames it holds: one opens at every key frame and every change, so the walk +// is over the key frames and the changes rather than the frames. +func (l layout) eachChunk(chunk func(first, samples int64)) { + t := l.stream.Timeline + for first := int64(0); first < t.Frames; { + last := min(t.Frames, (first/t.KeyEvery+1)*t.KeyEvery, (first/t.ChangeEvery+1)*t.ChangeEvery) + chunk(first, last-first) + first = last + } +} + +// boundContent is the media data when every picture takes its whole reserve - +// what planning promises, because no film of this stream comes to more. +func (l layout) boundContent() int64 { + bound := l.stream.BoundBytes() + var total int64 + for k, n := range l.counts { + total += n * int64(bound[k]) + } + return total +} + +// front is where the media data's content starts: the file type, the movie +// box and the media data's header. +func (l layout) front() int64 { + head := int64(isobmff.BoxHeader) + if l.bigData { + head += 8 + } + return int64(len(l.ftyp)) + l.moovLen() + head +} + +// need is the file without its padding when every picture takes its whole +// reserve, and with the smallest free box, which every one of these carries. +func (l layout) need() int64 { return l.front() + l.boundContent() + minFree } + +// The sample tables, each a full box. +func (l layout) stssLen() int64 { return fullBoxHeader + 4 + syncEntry*l.counts[video.KeySample] } +func (l layout) sdtpLen() int64 { return fullBoxHeader + l.stream.Frames } +func (l layout) stscLen() int64 { return fullBoxHeader + 4 + chunkRun*l.runs } +func (l layout) stszLen() int64 { return fullBoxHeader + 8 + sizeEntry*l.stream.Frames } + +func (l layout) stcoLen() int64 { + entry := int64(narrowChunk) + if l.wide { + entry = wideChunk + } + return fullBoxHeader + 4 + entry*l.chunks +} + +func (l layout) stblLen() int64 { + return isobmff.BoxHeader + int64(len(l.stsd)+len(l.stts)) + l.stssLen() + l.sdtpLen() + l.stscLen() + l.stszLen() + l.stcoLen() +} + +func (l layout) minfLen() int64 { + return isobmff.BoxHeader + int64(len(l.vmhd)+len(l.dinf)) + l.stblLen() +} + +func (l layout) mdiaLen() int64 { + return isobmff.BoxHeader + int64(len(l.mdhd)+len(l.hdlr)) + l.minfLen() +} + +func (l layout) trakLen() int64 { return isobmff.BoxHeader + int64(len(l.tkhd)) + l.mdiaLen() } +func (l layout) moovLen() int64 { return isobmff.BoxHeader + int64(len(l.mvhd)) + l.trakLen() } + +// compressor is the sample entry's compressor name, the one the AV1 binding +// RECOMMENDS (v1.3.0 section 2.2.4): a length, then the name, in thirty two +// bytes. MP4 has no field that has to name the program, and WebM names it +// only because Matroska requires a MuxingApp. +const compressor = "AOM Coding" + +func boolCount(b bool) int64 { + if b { + return 1 + } + return 0 +} + +func fileType() []byte { + b := boxStart("ftyp") + b = append(b, "isom"...) + b = binary.BigEndian.AppendUint32(b, 0x200) + // av01 SHALL be among the compatible brands (AV1 binding section 2.1). + b = append(b, "isomiso6av01mp41"...) + return boxEnd(b) +} + +// unity is the identity transformation matrix both headers carry. +func appendUnity(b []byte) []byte { + for _, v := range [9]uint32{0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000} { + b = binary.BigEndian.AppendUint32(b, v) + } + return b +} + +// movieHeader counts in milliseconds, which every length a film has is a +// whole number of. Its times are nought, so a file made today and the same +// one made tomorrow are the same bytes (D11). +func movieHeader(s video.Stream) []byte { + b := fullBoxStart("mvhd", 0, 0) + b = appendU32s(b, 0, 0, 1000, uint32(s.DurationMs), 0x00010000) + b = binary.BigEndian.AppendUint16(b, 0x0100) + b = append(b, make([]byte, 10)...) + b = appendUnity(b) + b = append(b, make([]byte, 24)...) + return boxEnd(binary.BigEndian.AppendUint32(b, 2)) +} + +// trackHeader is the one track, enabled and in the movie, with the picture's +// size in sixteen dot sixteen. +func trackHeader(s video.Stream) []byte { + b := fullBoxStart("tkhd", 0, 3) + b = appendU32s(b, 0, 0, 1, 0, uint32(s.DurationMs), 0, 0) + b = append(b, make([]byte, 8)...) + b = appendUnity(b) + return boxEnd(appendU32s(b, uint32(s.Width)<<16, uint32(s.Height)<<16)) +} + +// mediaHeader counts in frames - the time scale is the frame rate, a whole +// number (video.Properties), so every frame lasts one tick. The language is +// und, packed as ISO 639-2 three times five bits. +func mediaHeader(s video.Stream) []byte { + b := fullBoxStart("mdhd", 0, 0) + b = appendU32s(b, 0, 0, uint32(s.FPS), uint32(s.Frames)) + b = binary.BigEndian.AppendUint16(b, 0x55C4) + return boxEnd(binary.BigEndian.AppendUint16(b, 0)) +} + +func handler() []byte { + b := fullBoxStart("hdlr", 0, 0) + b = binary.BigEndian.AppendUint32(b, 0) + b = append(b, "vide"...) + b = append(b, make([]byte, 12)...) + return boxEnd(append(b, "VideoHandler\x00"...)) +} + +func videoHeader() []byte { + return boxEnd(append(fullBoxStart("vmhd", 0, 1), make([]byte, 8)...)) +} + +// dataInformation says the media data is in this file. +func dataInformation() []byte { + url := boxEnd(fullBoxStart("url ", 0, 1)) + dref := boxEnd(append(binary.BigEndian.AppendUint32(fullBoxStart("dref", 0, 0), 1), url...)) + return boxEnd(append(boxStart("dinf"), dref...)) +} + +// sampleDescription is the one AV1 sample entry: the picture's size, which +// SHALL be the sequence header's (AV1 binding section 2.2.4), the codec +// configuration with the sequence header in it, and the colour - the numbers +// the sequence header uses, said again for a player that reads the container's +// description rather than the stream's, as the binding says the entry SHOULD +// (section 2.3.4). +func sampleDescription(s video.Stream) []byte { + e := boxStart("av01") + e = append(e, make([]byte, 6)...) + e = binary.BigEndian.AppendUint16(e, 1) // data_reference_index + e = append(e, make([]byte, 16)...) + e = binary.BigEndian.AppendUint16(e, uint16(s.Width)) + e = binary.BigEndian.AppendUint16(e, uint16(s.Height)) + e = appendU32s(e, 0x00480000, 0x00480000, 0) + e = binary.BigEndian.AppendUint16(e, 1) // frame_count + name := make([]byte, 32) + name[0] = byte(len(compressor)) + copy(name[1:], compressor) + e = append(e, name...) + e = binary.BigEndian.AppendUint16(e, 0x0018) + e = binary.BigEndian.AppendUint16(e, 0xFFFF) + e = append(e, boxEnd(append(boxStart("av1C"), s.Config()...))...) + colr := append(boxStart("colr"), "nclx"...) + colr = binary.BigEndian.AppendUint16(colr, 1) // BT.709 primaries + colr = binary.BigEndian.AppendUint16(colr, 1) // BT.709 transfer + colr = binary.BigEndian.AppendUint16(colr, 1) // BT.709 matrix + e = append(e, boxEnd(append(colr, 0))...) // studio range + e = boxEnd(e) + return boxEnd(append(binary.BigEndian.AppendUint32(fullBoxStart("stsd", 0, 0), 1), e...)) +} + +// timeToSample is one run: every frame lasts one tick of the frame rate. +func timeToSample(s video.Stream) []byte { + b := binary.BigEndian.AppendUint32(fullBoxStart("stts", 0, 0), 1) + return boxEnd(appendU32s(b, uint32(s.Frames), 1)) +} + +// boxStart begins a box whose length boxEnd fills in once its content is +// there - for the small boxes built whole. The tables are written as a stream +// and their length counted instead. +func boxStart(kind string) []byte { + b := make([]byte, 4, 64) + return append(b, kind...) +} + +func fullBoxStart(kind string, version byte, flags uint32) []byte { + return append(boxStart(kind), version, byte(flags>>16), byte(flags>>8), byte(flags)) +} + +func boxEnd(b []byte) []byte { + binary.BigEndian.PutUint32(b, uint32(len(b))) + return b +} + +func appendU32s(b []byte, vs ...uint32) []byte { + for _, v := range vs { + b = binary.BigEndian.AppendUint32(b, v) + } + return b +} diff --git a/internal/format/mp4/mp4.go b/internal/format/mp4/mp4.go new file mode 100644 index 00000000..6cdb03f8 --- /dev/null +++ b/internal/format/mp4/mp4.go @@ -0,0 +1,423 @@ +// Package mp4 generates MP4 films: AV1 in ISO base media boxes, with no sound. +// +// The pictures and the stream are internal/format/video's, shared with WebM. +// What is here is the container: the file type, the movie box with the one +// track and its sample tables, the media data, and free boxes that carry the +// padding. +// +// MP4 describes every sample before the media data - its length in stsz, its +// place in stco - and the movie box goes first, so a browser can start playing +// before the file is in (the probe of docs/WIDEO-2026-10-06.md section 10). +// The pictures of a film differ, and how long each one is is known only once +// it is coded. So a film is coded twice: once for the lengths, which the movie +// box is written from, and again for the bytes, taking every tile the first +// pass kept (video.Pictures.Again). Coding a film is seconds since its pictures +// are cut into tiles - an hour of 1080p changing every second in under one - +// so the second pass costs seconds (docs/MP4-2026-10-07.md section 2, the +// owner's decision of the same day). +// +// The padding channel is free boxes at the end, after the media data: nothing +// in the movie box points past it, so it can grow a byte at a time. Measured +// with the probe at 8, 9, 15, 16 and 1 048 576 bytes and with none. A free box +// is always written, at least its eight bytes, which leaves no size above the +// minimum that cannot be reached, and one larger than a box length can say is +// several (internal/format/isobmff). +package mp4 + +import ( + "context" + "encoding/binary" + "fmt" + "io" + + "github.com/donislawdev/TestingFilesGenerator/internal/core" + "github.com/donislawdev/TestingFilesGenerator/internal/format" + "github.com/donislawdev/TestingFilesGenerator/internal/format/isobmff" + "github.com/donislawdev/TestingFilesGenerator/internal/format/video" +) + +const ( + id = "mp4" + generatorVersion = "1" + + // minFree is the smallest free box: its header and nothing in it. + minFree = isobmff.BoxHeader +) + +func init() { + format.Register(format.Descriptor{ + ID: id, + Name: "MP4 video", + Extension: ".mp4", + Fidelity: format.FidelityFull, + Determinism: format.DeterminismByte, + + MinBytes: minimumBytes(), + + // WebM's ceiling and for WebM's reason: every picture is coded on its + // own, a tile a call to gav1d, and gav1d allocates twenty odd objects + // a call (internal/format/webm). The second pass takes every tile the + // first one kept, so the default film calls gav1d no more often than + // its WebM does. Measured on 2026-10-07, lowest of the guard's rounds: + // 938 to 941 objects at one, four and sixteen threads (WebM 898 to + // 904), the sample lengths and the second pass's crew the difference - + // and 1241 with one object allocated per frame, the defect the ceiling + // exists for. 1024 sits between the two. + AllocCeiling: 1024, + + Padding: format.PaddingChannel{ + Name: "free boxes after the pictures at the end of the file", + Where: format.PlacementEnd, + Capacity: 0, + }, + Label: format.LabelVisible, + Oracle: "libaom", + Properties: video.Properties(), + JointLimits: video.JointLimits(), + GeneratorVersion: generatorVersion, + Generator: generator{}, + }) +} + +// minimumBytes is the smallest film at the default settings - the smallest +// rung's bound, ten seconds at thirty frames, one key frame, a new picture +// every second - with its free box. Worked out from the same layout planning +// uses rather than written down, so the two cannot disagree. +func minimumBytes() int64 { + t, err := video.NewTimeline(id, 10_000, 60_000, 1_000, 30) + if err != nil { + panic(err) + } + smallest := video.Ladder[len(video.Ladder)-1] + return newLayout(video.NewStream(t, smallest.Ceiling, smallest.Width, smallest.Height, "")).need() +} + +type generator struct{} + +func (generator) Plan(r format.Request) (format.Plan, error) { + need := func(st video.Stream) int64 { return newLayout(st).need() } + return video.Plan(id, r, need, belowMinimum) +} + +// belowMinimum names the four things D6 asks for, and the knobs that make a +// film smaller (video.Hint). +func belowMinimum(requested, need int64, c video.Choice, s video.Settings) error { + return &format.BelowMinimumError{ + Format: "MP4", + Requested: requested, + Minimum: need, + Reason: core.Says("mp4.MinimumReasonPictures", + "a film of %s at %d frames a second with a key frame every %s and a new %dx%d picture every %s takes %d B, and the file always carries a free box, which costs %d B even when it holds nothing", + core.A("Duration", core.FormatDuration(s.DurationMs)), core.A("FPS", s.FPS), + core.A("Interval", core.FormatDuration(s.KeyEvery*1000/int64(s.FPS))), + core.A("Width", c.Width), core.A("Height", c.Height), core.A("Change", core.FormatDuration(s.ChangeMs())), + core.A("Bytes", need-minFree), core.A("Free", minFree)), + Hint: video.Hint(need, c), + } +} + +func (generator) Write(ctx context.Context, w io.Writer, p format.Plan) error { + f, ok := p.Memo.(video.Film) + if !ok { + return core.Defect(fmt.Errorf("mp4: the plan was not produced by this generator")) + } + st := f.Stream() + l := newLayout(st) + z, pics, err := measure(ctx, f, st) + if err != nil { + return err + } + defer pics.Close() + + o := &out{w: w, buf: make([]byte, 0, 64<<10)} + content := l.writeMovie(o, z) + o.mediaHeader(l.bigData, content) + if err := writeSamples(ctx, o, l, pics); err != nil { + return err + } + if o.flush(); o.err != nil { + return o.err + } + // The padding is what the first pass's lengths leave, not what the second + // pass wrote, so a second pass that came out different leaves the file a + // different size from the plan - which the engine refuses - rather than a + // movie box that describes samples it does not carry. + pad := p.Bytes - l.front() - content + if pad < minFree { + return core.Defect(fmt.Errorf("mp4: the pictures came to %d B of media data and the file is %d B, which leaves no room for the free box every one of these carries", + content, p.Bytes)) + } + return isobmff.WritePadding(ctx, w, f.Choice.Seed, pad) +} + +// sizes is how long each picture's two samples came out in the first pass, by +// change - a hundred thousand changes at most, eight bytes each. +type sizes struct { + t video.Timeline + show int64 + // pictures is the key sample and the copy sample of each change. + pictures [][2]uint32 +} + +// of is how long frame i's sample is. +func (z sizes) of(i int64) int64 { + k := z.t.SampleAt(i) + if k == video.ShowSample { + return z.show + } + return int64(z.pictures[z.t.ChangeOf(i)][k]) +} + +// chunk is the media data of the chunk of samples frames opening at frame +// first. Only its first two frames can carry a picture (layout.eachChunk). +func (z sizes) chunk(first, samples int64) int64 { + n := z.of(first) + if samples > 1 { + n += z.of(first + 1) + } + if rest := samples - 2; rest > 0 { + n += rest * z.show + } + return n +} + +// measure is the first pass: every picture coded once, in order, for how long +// its samples are, and the coder of the second pass. Nothing of the file is +// written yet, so a picture over its reserve is refused before the file has a +// byte. The work coding is worth is reported here, and the second pass, +// which codes only what this one could not keep, reports none. +// +// The first pass is closed on every way out - an error, a stop, and a panic +// in coding, which the engine turns into the run's error and which would +// otherwise leave this pass's helpers running. Closing it twice is harmless, +// and Again closes it too. +func measure(ctx context.Context, f video.Film, st video.Stream) (sizes, *video.Pictures, error) { + first := f.Choice.Pictures(st) + defer first.Close() + z := sizes{t: st.Timeline, show: int64(st.BoundBytes()[video.ShowSample]), pictures: make([][2]uint32, st.Changes())} + for c := range st.Changes() { + if err := ctx.Err(); err != nil { + return sizes{}, nil, err + } + if err := first.At(c); err != nil { + return sizes{}, nil, err + } + format.Worked(ctx, first.Work()) + n := first.SampleLens() + z.pictures[c] = [2]uint32{uint32(n[video.KeySample]), uint32(n[video.CopySample])} + } + again, err := first.Again() + if err != nil { + return sizes{}, nil, err + } + return z, again, nil +} + +// writeMovie writes the file type and the movie box, with the sample tables +// from the first pass's lengths, and gives back how long the media data is. +func (l layout) writeMovie(o *out, z sizes) int64 { + o.write(l.ftyp) + o.header("moov", l.moovLen()) + o.write(l.mvhd) + o.header("trak", l.trakLen()) + o.write(l.tkhd) + o.header("mdia", l.mdiaLen()) + o.write(l.mdhd) + o.write(l.hdlr) + o.header("minf", l.minfLen()) + o.write(l.vmhd) + o.write(l.dinf) + o.header("stbl", l.stblLen()) + o.write(l.stsd) + o.write(l.stts) + l.writeSyncs(o) + l.writeDependencies(o) + l.writeChunkRuns(o) + l.writeSizes(o, z) + return l.writeChunkPlaces(o, z) +} + +// writeSyncs is stss: every key frame, numbered from one. Each is a sample a +// player can start from - a shown key frame with the sequence header before +// it, as the AV1 binding asks of a sync sample (section 2.4). +func (l layout) writeSyncs(o *out) { + t := l.stream.Timeline + o.fullHeader("stss", l.stssLen()) + o.u32(uint32(l.counts[video.KeySample])) + for k := range l.counts[video.KeySample] { + o.u32(uint32(k*t.KeyEvery + 1)) + } +} + +// Dependencies, as sdtp writes sample_depends_on: a key frame and a hidden +// intra only copy depend on no other sample - the AV1 binding says an intra +// only frame SHOULD be signalled so (section 2.4) - and a frame that shows a +// picture again depends on the copy before it. +const ( + dependsOnNone = 2 << 4 + dependsOnOthers = 1 << 4 +) + +func (l layout) writeDependencies(o *out) { + t := l.stream.Timeline + o.fullHeader("sdtp", l.sdtpLen()) + for i := range t.Frames { + v := byte(dependsOnNone) + if t.SampleAt(i) == video.ShowSample { + v = dependsOnOthers + } + o.u8(v) + } +} + +// writeChunkRuns is stsc: one entry for each run of chunks holding the same +// number of samples, the way newLayout counted them. +func (l layout) writeChunkRuns(o *out) { + o.fullHeader("stsc", l.stscLen()) + o.u32(uint32(l.runs)) + chunk, last := int64(0), int64(-1) + l.eachChunk(func(_, samples int64) { + chunk++ + if samples != last { + o.u32(uint32(chunk)) + o.u32(uint32(samples)) + o.u32(1) + last = samples + } + }) +} + +func (l layout) writeSizes(o *out, z sizes) { + t := l.stream.Timeline + o.fullHeader("stsz", l.stszLen()) + o.u32(0) + o.u32(uint32(t.Frames)) + for i := range t.Frames { + o.u32(uint32(z.of(i))) + } +} + +// writeChunkPlaces is stco, or co64 when the layout asked for eight bytes, and +// gives back where the last chunk ends, counted from the media data's start. +func (l layout) writeChunkPlaces(o *out, z sizes) int64 { + kind := "stco" + if l.wide { + kind = "co64" + } + o.fullHeader(kind, l.stcoLen()) + o.u32(uint32(l.chunks)) + front, content := l.front(), int64(0) + l.eachChunk(func(first, samples int64) { + if l.wide { + o.u64(uint64(front + content)) + } else { + o.u32(uint32(front + content)) + } + content += z.chunk(first, samples) + }) + return content +} + +// writeSamples is the second pass: every frame's sample, chunk by chunk, the +// picture of each chunk made as it opens. +func writeSamples(ctx context.Context, o *out, l layout, pics *video.Pictures) error { + t := l.stream.Timeline + var err error + l.eachChunk(func(first, samples int64) { + if err != nil { + return + } + if err = ctx.Err(); err != nil { + return + } + if err = pics.At(t.ChangeOf(first)); err != nil { + return + } + frame := pics.Samples() + for i := first; i < first+samples; i++ { + o.write(frame[t.SampleAt(i)]) + } + }) + if err == nil { + err = o.err + } + return err +} + +// out keeps the first error, so a long run of small writes checks once, and +// gathers the sample tables - four bytes a frame, five million frames in a +// day - into one buffer rather than a call each. +type out struct { + w io.Writer + err error + buf []byte +} + +func (o *out) flush() { + if o.err == nil && len(o.buf) > 0 { + _, o.err = o.w.Write(o.buf) + } + o.buf = o.buf[:0] +} + +// room makes room for n more bytes in the buffer. +func (o *out) room(n int) { + if len(o.buf)+n > cap(o.buf) { + o.flush() + } +} + +// write takes a piece larger than half the buffer straight through, after +// what the buffer holds - a picture can be megabytes. +func (o *out) write(b []byte) { + if len(b) > cap(o.buf)/2 { + o.flush() + if o.err == nil { + _, o.err = o.w.Write(b) + } + return + } + o.room(len(b)) + o.buf = append(o.buf, b...) +} + +func (o *out) u8(v byte) { + o.room(1) + o.buf = append(o.buf, v) +} + +func (o *out) u32(v uint32) { + o.room(4) + o.buf = binary.BigEndian.AppendUint32(o.buf, v) +} + +func (o *out) u64(v uint64) { + o.room(8) + o.buf = binary.BigEndian.AppendUint64(o.buf, v) +} + +// header is a box's header, its length counted rather than built. +func (o *out) header(kind string, length int64) { + o.u32(uint32(length)) + o.room(len(kind)) + o.buf = append(o.buf, kind...) +} + +// fullHeader is a full box's header, version nought and no flags - every +// table here is one. +func (o *out) fullHeader(kind string, length int64) { + o.header(kind, length) + o.u32(0) +} + +// mediaHeader is the media data's header: its length in four bytes, or in +// eight after a length of one when the layout said a film of this stream could +// need them. +func (o *out) mediaHeader(big bool, content int64) { + if !big { + o.header("mdat", isobmff.BoxHeader+content) + return + } + o.header("mdat", 1) + o.u64(uint64(isobmff.BoxHeader + 8 + content)) +} diff --git a/internal/format/video/ahead.go b/internal/format/video/ahead.go index c9f5e65f..146ec5ce 100644 --- a/internal/format/video/ahead.go +++ b/internal/format/video/ahead.go @@ -43,12 +43,21 @@ var helping atomic.Int64 // asked for them, since the process started. var codedBeside atomic.Int64 +// codedAll counts every tile coded since the process started, by a helper or +// by the goroutine that asked for it. +var codedAll atomic.Int64 + // Helpers is how many helpers are coding right now and how many tiles helpers // have coded since the process started. Guards read it: a film held to // the bytes of one goroutine says nothing unless several really coded it, and // a film abandoned half way has to leave none of them running. func Helpers() (running, coded int64) { return helping.Load(), codedBeside.Load() } +// Coded is how many tiles have been coded since the process started, by +// anybody. A guard reads it: an MP4 codes its film twice, and the second pass +// is to code nothing the first one kept (Pictures.Again). +func Coded() int64 { return codedAll.Load() } + // takeHelper claims a place for one more helper, if the process has one. func takeHelper() bool { limit := int64(runtime.GOMAXPROCS(0) - 1) @@ -132,6 +141,17 @@ func newCrew(f *film, ks keyer, qindex int, jobs int64) *crew { return c } +// again is a crew for a second pass over the same film, painting with this +// one's painter (Pictures.Again). This one has to be stopped first, because +// the painter is used by one goroutine at a time. +func (c *crew) again() *crew { + n := &crew{film: c.film, keys: c.keys, qindex: c.qindex, own: c.own, most: c.most, encode: c.encode} + if n.most > 0 { + n.queue = make(chan *job, n.ahead()*len(n.keys.tiles)) + } + return n +} + // ahead is how many pictures past the one being written may be offered: two // for each goroutine that codes, so none of them waits for the next picture // while the writer is still busy with the last. Nought without helpers - a @@ -183,6 +203,7 @@ func (c *crew) help() { func (c *crew) code(p *painter, j *job, beside bool) { defer j.done.Done() + codedAll.Add(1) defer func() { if v := recover(); v != nil { j.panicked = v diff --git a/internal/format/video/pictures.go b/internal/format/video/pictures.go index 85323e95..e1b6f56d 100644 --- a/internal/format/video/pictures.go +++ b/internal/format/video/pictures.go @@ -211,13 +211,47 @@ func (p *Pictures) jobFor(i int, l look) *job { // running once it returns. A helper in the middle of a tile finishes it first. func (p *Pictures) Close() { p.crew.stop() } -// KeySample is the current picture as a key frame, with the sequence header -// before it. It is written over by the next picture At makes. -func (p *Pictures) KeySample() []byte { return p.key } +// Again is the coder of the same film from its first picture, for a second +// pass once this one has made its last. MP4 says how long every picture is +// before it carries any of them (docs/MP4-2026-10-07.md section 2), so it +// codes a film once to learn that and again to write it. +// +// The second pass takes what the first one has: every tile it kept - the ones +// no picture changes, the square's places, the clock's while there was room - +// and its film and painter, so it codes only the clock's tiles the first had +// no room for, and holds no second copy of the picture. This one is closed by +// it, and asked for nothing after. +// +// Every tile kept is a finished one: each picture was made in order, which +// waited for all its tiles, and closing waited for every helper. +func (p *Pictures) Again() (*Pictures, error) { + if n := p.stream.Changes(); p.change != n-1 { + return nil, core.Defect(fmt.Errorf("video: a film was asked to start again at picture %d of %d, and only one that has made its last can", p.change, n)) + } + p.Close() + q := &Pictures{ + choice: p.choice, stream: p.stream, change: -1, + crew: p.crew.again(), + known: p.known, kept: p.kept, seen: p.seen, + tiles: p.tiles, spare: p.spare, + } + q.pending = make([]opened, 0, q.crew.ahead()+1) + return q, nil +} + +// Samples is the current picture's samples by kind (Timeline.SampleAt): as a +// key frame with the sequence header before it, as a hidden intra only copy +// with the frame that shows it, and the frame that shows it again. The first +// two are written over by the next picture At makes, and the third is shared +// and must not be written to. +func (p *Pictures) Samples() [SampleKinds][]byte { + return [SampleKinds][]byte{KeySample: p.key, CopySample: p.copied, ShowSample: p.stream.show} +} -// CopySample is the current picture as a hidden intra only copy, and the frame -// that shows it. It is written over by the next picture At makes. -func (p *Pictures) CopySample() []byte { return p.copied } +// SampleLens is how long each of the current picture's samples is. +func (p *Pictures) SampleLens() [SampleKinds]int { + return [SampleKinds]int{KeySample: len(p.key), CopySample: len(p.copied), ShowSample: len(p.stream.show)} +} // CodedSize is the bytes of the tiles of picture change of this choice's // film, each coded afresh under the film's layout - what a picture takes of diff --git a/internal/format/video/plan.go b/internal/format/video/plan.go new file mode 100644 index 00000000..96294978 --- /dev/null +++ b/internal/format/video/plan.go @@ -0,0 +1,84 @@ +package video + +import ( + "github.com/donislawdev/TestingFilesGenerator/internal/core" + "github.com/donislawdev/TestingFilesGenerator/internal/format" +) + +// Film is what planning settles for a film and writing makes it from: the +// picture and the settings. Both containers keep it as their plan's memo. +type Film struct { + Choice Choice + Settings Settings +} + +// Stream is the stream the film is written in, the one planning sized it by. +func (f Film) Stream() Stream { + return NewStream(f.Settings.Timeline, f.Choice.Ceiling, f.Choice.Width, f.Choice.Height, f.Choice.Label) +} + +// Plan settles a film for a request the way every container plans one, which +// differs between them only in what a stream costs and in the words of the +// refusal. +// +// need is the bytes a film of the stream takes in the container when every +// picture takes its whole reserve, with the smallest padding the container +// always writes - no film of that stream comes to more. refuse is the +// container's refusal when the smallest film it can make is larger than the +// request, naming what D6 asks for. +func Plan(formatID string, r format.Request, need func(Stream) int64, refuse func(requested, need int64, c Choice, s Settings) error) (format.Plan, error) { + label := "" + if r.Label { + label = core.Label(formatID, r.Bytes, r.Seed) + } + s, err := Read(formatID, r.Properties) + if err != nil { + return format.Plan{}, err + } + fits := func(st Stream) bool { return need(st) <= r.Bytes } + c, st, err := Choose(formatID, r, s, label, fits) + if err != nil { + return format.Plan{}, err + } + if n := need(st); n > r.Bytes { + return format.Plan{}, refuse(r.Bytes, n, c, s) + } + + p := format.Plan{ + Bytes: r.Bytes, + Exact: true, + Determinism: format.DeterminismByte, + Properties: Facts(c, s), + Memo: Film{Choice: c, Settings: s}, + Work: st.Work(), + } + if r.Label && !c.Labelled() { + p.Notes = append(p.Notes, format.Note{ + Code: "label_omitted", + Detail: core.Says("format.ThePictureIsPxWideAnd", "The picture is %d px wide and the label needs more room, so this file carries no visible label. Its name and the manifest still identify it.", core.A("Width", c.Width)), + }) + } + if !c.ClockShown(s.Timeline) { + p.Notes = append(p.Notes, format.Note{ + Code: "clock_omitted", + Detail: core.Says("video.NoClock", "The picture is %dx%d and the clock needs more room, so this film shows no clock. The square still steps across it where it has room to move.", + core.A("Width", c.Width), core.A("Height", c.Height)), + }) + } + return p, nil +} + +// Hint is what a refusal below a film's minimum offers. A smaller picture only +// when the request named one: a picture chosen to fit is already the smallest +// that would, so offering a smaller one would send somebody to a setting that +// changes nothing. Every other knob is a setting, so each is named. +func Hint(need int64, c Choice) core.Said { + if c.Named { + return core.Says("video.AskForBOrMorePictures", + "Ask for %d B or more, or a shorter film, fewer frames a second, key frames further apart, a new picture less often or a smaller picture", + core.A("Floor", need)) + } + return core.Says("video.AskForBOrMoreFilmPictures", + "Ask for %d B or more, or a shorter film, fewer frames a second, key frames further apart or a new picture less often", + core.A("Floor", need)) +} diff --git a/internal/format/video/stream.go b/internal/format/video/stream.go index 928d8871..f9936ece 100644 --- a/internal/format/video/stream.go +++ b/internal/format/video/stream.go @@ -85,17 +85,13 @@ func (s frameShape) frameLens(tiles int) (key, hidden int) { // what MP4 puts in av1C and Matroska in CodecPrivate. func (s Stream) Config() []byte { return s.config } -// ShowSample is a frame that shows the current picture again. Shared between -// frames, and must not be written to. -func (s Stream) ShowSample() []byte { return s.show } - // BoundBytes is the longest each of the three kinds of sample can be - a key // frame, a hidden copy with the frame that shows it, and a frame that shows a // picture again - for a container's arithmetic to plan with before any picture // is coded. -func (s Stream) BoundBytes() (key, copied, shown int) { +func (s Stream) BoundBytes() [SampleKinds]int { k, h := s.shape.frameLens(s.Reserve) - return len(s.seq) + k, h + len(s.show), len(s.show) + return [SampleKinds]int{KeySample: len(s.seq) + k, CopySample: h + len(s.show), ShowSample: len(s.show)} } // workPerPixel is how many bytes of writing coding one pixel of a picture is diff --git a/internal/format/video/timeline.go b/internal/format/video/timeline.go index 2c7c6f3a..c6836efa 100644 --- a/internal/format/video/timeline.go +++ b/internal/format/video/timeline.go @@ -117,6 +117,59 @@ func (t Timeline) CodedPerSecond() int64 { return min(n, fps, t.Frames) } +// The three kinds of sample a frame carries, in both containers. +const ( + KeySample = iota // the picture as a key frame, the sequence header first + CopySample // the picture as a hidden copy, and the frame showing it + ShowSample // the current picture shown again + SampleKinds // how many kinds there are +) + +// SampleAt is the kind of sample frame i carries. +// +// A key frame carries the picture as a key frame. The frame after it carries +// the hidden copy, because a shown key frame cannot be shown a second time +// (doc.go), and so does the frame that opens a change, which is the first to +// show its picture. Every other frame shows the current picture again. +func (t Timeline) SampleAt(i int64) int { + switch { + case t.IsKey(i): + return KeySample + case t.StartsChange(i), t.IsKey(i - 1): + return CopySample + } + return ShowSample +} + +// SampleCounts is how many frames of the film carry each kind of sample - +// what SampleAt says of every frame, counted over the key frames and the +// changes rather than the frames, so a day of frames is counted in the time of +// its hundred thousand changes. +func (t Timeline) SampleCounts() [SampleKinds]int64 { + var copies int64 + // The first change opens on the first frame, which is a key frame. + for c := int64(1); c < t.Changes(); c++ { + if !t.IsKey(c * t.ChangeEvery) { + copies++ + } + } + // The frame after a key frame, unless it is a key frame itself or opens + // a change, which the count above has. + for k := range t.Keys() { + i := k*t.KeyEvery + 1 + copies += boolCount(i < t.Frames && !t.IsKey(i) && !t.StartsChange(i)) + } + keys := t.Keys() + return [SampleKinds]int64{KeySample: keys, CopySample: copies, ShowSample: t.Frames - keys - copies} +} + +func boolCount(b bool) int64 { + if b { + return 1 + } + return 0 +} + // StartMs is when frame i is shown, in whole milliseconds rounded to the // nearest, the way a container that counts in milliseconds stores it. func (t Timeline) StartMs(i int64) int64 { diff --git a/internal/format/webm/layout.go b/internal/format/webm/layout.go index 508e7547..3e03ed66 100644 --- a/internal/format/webm/layout.go +++ b/internal/format/webm/layout.go @@ -82,43 +82,22 @@ func (l layout) next(first int64) int64 { return min(s.Frames, (first/s.KeyEvery+1)*s.KeyEvery, (first/s.ChangeEvery+1)*s.ChangeEvery, first+span) } -// The three kinds of sample a frame carries. -const ( - keySample = iota // the picture as a key frame, the sequence header first - copySample // the picture as a hidden copy, and the frame showing it - showSample // the current picture shown again - kinds -) - -// kindAt is what frame i of the cluster opening at frame first carries. -// -// Only a cluster's first two frames can carry a picture. A key frame opens its -// cluster, and the frame after it carries the hidden copy, because a shown key -// frame cannot be shown again - unless that frame opens a change, which opens -// a cluster of its own. A change opens its cluster with the copy and the frame -// that shows it. Every other frame shows the current picture again. -func (l layout) kindAt(first, i int64) int { - s := l.stream - switch { - case i == first && s.IsKey(i): - return keySample - case i == first && s.StartsChange(i), i == first+1 && s.IsKey(first): - return copySample - } - return showSample -} - // clusterContent is what the cluster of frames [first, last) holds, given how -// long each kind of sample is - counted from kindAt for the two frames that can -// carry a picture, so the length written before a cluster and the blocks -// written in it come from one answer. -func (l layout) clusterContent(first, last int64, lens [kinds]int) uint64 { +// long each kind of sample is (video.Timeline.SampleAt), so the length written +// before a cluster and the blocks written in it come from one answer. +// +// Only a cluster's first two frames can carry a picture. A key frame and a +// change each open a cluster, and the frame after a key frame - the only +// other one that carries a picture - is the second of the key frame's cluster +// or opens a change and its own, because a cluster runs thirty seconds and a +// second is at least one frame. +func (l layout) clusterContent(first, last int64, lens [video.SampleKinds]int) uint64 { total := uintElementLen(idTimestamp, uint64(l.stream.StartMs(first))) for i := first; i < min(first+2, last); i++ { - total += blockLen(lens[l.kindAt(first, i)]) + total += blockLen(lens[l.stream.SampleAt(i)]) } if rest := last - first - 2; rest > 0 { - total += uint64(rest) * blockLen(lens[showSample]) + total += uint64(rest) * blockLen(lens[video.ShowSample]) } return total } @@ -127,8 +106,7 @@ func (l layout) clusterContent(first, last int64, lens [kinds]int) uint64 { // reserve - what planning promises, because no film of this stream comes to // more. func (l layout) boundBytes() int64 { - key, copied, shown := l.stream.BoundBytes() - lens := [kinds]int{keySample: key, copySample: copied, showSample: shown} + lens := l.stream.BoundBytes() body := l.front() + l.cuesLen() for first := int64(0); first < l.stream.Frames; { last := l.next(first) diff --git a/internal/format/webm/webm.go b/internal/format/webm/webm.go index e1f7a38d..4564d533 100644 --- a/internal/format/webm/webm.go +++ b/internal/format/webm/webm.go @@ -100,56 +100,13 @@ func minimumBytes() int64 { type generator struct{} -type memo struct { - choice video.Choice - settings video.Settings - total int64 -} - func (generator) Plan(r format.Request) (format.Plan, error) { - label := "" - if r.Label { - label = core.Label(id, r.Bytes, r.Seed) - } - s, err := video.Read(id, r.Properties) - if err != nil { - return format.Plan{}, err - } - fits := func(st video.Stream) bool { return newLayout(st).boundBytes()+minVoid <= r.Bytes } - c, st, err := video.Choose(id, r, s, label, fits) - if err != nil { - return format.Plan{}, err - } - if need := newLayout(st).boundBytes() + minVoid; need > r.Bytes { - return format.Plan{}, belowMinimum(r.Bytes, need, c, s) - } - - p := format.Plan{ - Bytes: r.Bytes, - Exact: true, - Determinism: format.DeterminismByte, - Properties: video.Facts(c, s), - Memo: memo{choice: c, settings: s, total: r.Bytes}, - Work: st.Work(), - } - if r.Label && !c.Labelled() { - p.Notes = append(p.Notes, format.Note{ - Code: "label_omitted", - Detail: core.Says("format.ThePictureIsPxWideAnd", "The picture is %d px wide and the label needs more room, so this file carries no visible label. Its name and the manifest still identify it.", core.A("Width", c.Width)), - }) - } - if !c.ClockShown(s.Timeline) { - p.Notes = append(p.Notes, format.Note{ - Code: "clock_omitted", - Detail: core.Says("video.NoClock", "The picture is %dx%d and the clock needs more room, so this film shows no clock. The square still steps across it where it has room to move.", - core.A("Width", c.Width), core.A("Height", c.Height)), - }) - } - return p, nil + need := func(st video.Stream) int64 { return newLayout(st).boundBytes() + minVoid } + return video.Plan(id, r, need, belowMinimum) } // belowMinimum names the four things D6 asks for, and the knobs that make a -// film smaller - every one of them is a setting, so each is named. +// film smaller (video.Hint). func belowMinimum(requested, need int64, c video.Choice, s video.Settings) error { return &format.BelowMinimumError{ Format: "WebM", @@ -161,26 +118,12 @@ func belowMinimum(requested, need int64, c video.Choice, s video.Settings) error core.A("Interval", core.FormatDuration(s.KeyEvery*1000/int64(s.FPS))), core.A("Width", c.Width), core.A("Height", c.Height), core.A("Change", core.FormatDuration(s.ChangeMs())), core.A("Bytes", need-minVoid), core.A("Void", minVoid)), - Hint: hint(need, c), - } -} - -// hint offers a smaller picture only when the request named one. A picture -// chosen to fit is already the smallest that would, so offering a smaller -// one would send somebody to a setting that changes nothing. -func hint(need int64, c video.Choice) core.Said { - if c.Named { - return core.Says("webm.AskForBOrMorePictures", - "Ask for %d B or more, or a shorter film, fewer frames a second, key frames further apart, a new picture less often or a smaller picture", - core.A("Floor", need)) + Hint: video.Hint(need, c), } - return core.Says("webm.AskForBOrMoreFilmPictures", - "Ask for %d B or more, or a shorter film, fewer frames a second, key frames further apart or a new picture less often", - core.A("Floor", need)) } func (generator) Write(ctx context.Context, w io.Writer, p format.Plan) error { - m, ok := p.Memo.(memo) + f, ok := p.Memo.(video.Film) if !ok { return core.Defect(fmt.Errorf("webm: the plan was not produced by this generator")) } @@ -189,9 +132,9 @@ func (generator) Write(ctx context.Context, w io.Writer, p format.Plan) error { return ctx.Err() default: } - st := video.NewStream(m.settings.Timeline, m.choice.Ceiling, m.choice.Width, m.choice.Height, m.choice.Label) + st := f.Stream() l := newLayout(st) - body := uint64(m.total - l.outside()) + body := uint64(p.Bytes - l.outside()) cuesAt := body - l.cuesLen() out := &sticky{w: w} @@ -201,7 +144,7 @@ func (generator) Write(ctx context.Context, w io.Writer, p format.Plan) error { out.write(seekHead(l.seekHeadLen, l.seekHeadLen+uint64(len(l.info)), cuesAt)) out.write(l.info) out.write(l.tracks) - pics := m.choice.Pictures(st) + pics := f.Choice.Pictures(st) defer pics.Close() keys, end, err := writeClusters(ctx, out, l, pics) if err != nil { @@ -212,7 +155,7 @@ func (generator) Write(ctx context.Context, w io.Writer, p format.Plan) error { return core.Defect(fmt.Errorf("webm: the clusters came to %d B and the Cues are to start at %d B, which leaves no room for the Void every one of these carries", end, cuesAt)) } - if err := writeVoid(ctx, w, void, m.choice.Seed); err != nil { + if err := writeVoid(ctx, w, void, f.Choice.Seed); err != nil { return err } writeCues(out, l, keys) @@ -284,19 +227,19 @@ func writeClusters(ctx context.Context, out *sticky, l layout, pics *video.Pictu // writeCluster writes the cluster of frames [first, last) and returns its // length: its time, then a SimpleBlock for each frame - track 1, the frame's // time from the cluster's, and the key flag on a key frame. Which sample each -// frame carries is the layout's kindAt, the same answer the cluster's length -// was counted from. +// frame carries is video.Timeline.SampleAt, the same answer the cluster's +// length was counted from. func writeCluster(out *sticky, l layout, pics *video.Pictures, first, last int64) uint64 { s := l.stream - samples := [kinds][]byte{keySample: pics.KeySample(), copySample: pics.CopySample(), showSample: s.ShowSample()} - content := l.clusterContent(first, last, [kinds]int{len(samples[keySample]), len(samples[copySample]), len(samples[showSample])}) + samples := pics.Samples() + content := l.clusterContent(first, last, pics.SampleLens()) ts := s.StartMs(first) out.header(idCluster, content) out.uint(idTimestamp, uint64(ts)) var block [4]byte block[0] = 0x81 for i := first; i < last; i++ { - sample := samples[l.kindAt(first, i)] + sample := samples[s.SampleAt(i)] rel := s.StartMs(i) - ts block[1], block[2], block[3] = byte(rel>>8), byte(rel), 0 if s.IsKey(i) { diff --git a/internal/guard/film_test.go b/internal/guard/film_test.go index d8e44121..15b42420 100644 --- a/internal/guard/film_test.go +++ b/internal/guard/film_test.go @@ -1,10 +1,8 @@ package guard import ( - "encoding/binary" "errors" "fmt" - "math" "os" "path/filepath" "slices" @@ -21,9 +19,10 @@ import ( // of animation_test.go, where a GIF the plan called animated came out still. // // The reader knows WebM's elements and AV1's headers as far as the questions -// below need, and no further. What it does not understand it refuses, so a -// change in the writer that this reader cannot follow fails loudly rather than -// being read as something it is not. +// below need, and no further, and filmmp4_test.go reads MP4 into the same +// shape. What it does not understand it refuses, so a change in the writer +// that this reader cannot follow fails loudly rather than being read as +// something it is not. type filmBlock struct { ts int64 @@ -31,16 +30,37 @@ type filmBlock struct { data []byte } -type webmFilm struct { +// filmRead is a film of either container, as the questions below ask it. +type filmRead struct { durationMs float64 width int height int codecPrivate []byte blocks []filmBlock - cuePoints int - // tail is the IDs of the segment's last two elements, the padding and + // index is how many places a player can start from the container lists: + // the cue points of a WebM, the sync samples of an MP4. + index int + // tail is the IDs of a WebM segment's last two elements, the padding and // then the Cues (docs/WIDEO-2026-10-06.md section 15). tail [2]uint64 + // ends says what is wrong with how the file ends - where its padding + // sits - and is empty when nothing is. + ends string +} + +// filmFormats are the containers a film is made in. The questions of the +// container are asked of each. +var filmFormats = []string{"webm", "mp4"} + +// walkFilm reads a film of either container. +func walkFilm(id string, b []byte) (filmRead, error) { + switch id { + case "webm": + return walkWebM(b) + case "mp4": + return walkMP4(b) + } + return filmRead{}, fmt.Errorf("no reader for films in %s", id) } func filmOne(t *testing.T, target engine.Target) ([]byte, map[string]any) { @@ -49,147 +69,6 @@ func filmOne(t *testing.T, target engine.Target) ([]byte, map[string]any) { return b, facts } -// ebmlVint reads an element ID (keeping its marker) or a size (without it). -func ebmlVint(b []byte, keepMarker bool) (value uint64, length int, err error) { - if len(b) == 0 { - return 0, 0, fmt.Errorf("the file ends where an element should start") - } - length = 1 - for mask := byte(0x80); length <= 8 && b[0]&mask == 0; mask >>= 1 { - length++ - } - if length > 8 || length > len(b) { - return 0, 0, fmt.Errorf("a variable length number runs past the end") - } - value = uint64(b[0]) - if !keepMarker { - value &= uint64(0xFF >> length) - } - for _, c := range b[1:length] { - value = value<<8 | uint64(c) - } - return value, length, nil -} - -// ebmlElements walks a run of elements, each exactly covering the next, and -// refuses one whose size runs past the run. -func ebmlElements(b []byte, each func(id uint64, body []byte) error) error { - for pos := 0; pos < len(b); { - id, n, err := ebmlVint(b[pos:], true) - if err != nil { - return err - } - size, m, err := ebmlVint(b[pos+n:], false) - if err != nil { - return err - } - start := pos + n + m - if uint64(len(b)-start) < size { - return fmt.Errorf("element %#x at %d says %d B and %d remain", id, pos, size, len(b)-start) - } - if err := each(id, b[start:start+int(size)]); err != nil { - return err - } - pos = start + int(size) - } - return nil -} - -func beUint(b []byte) uint64 { - var v uint64 - for _, c := range b { - v = v<<8 | uint64(c) - } - return v -} - -func walkWebM(b []byte) (webmFilm, error) { - var f webmFilm - sawSegment := false - err := ebmlElements(b, func(id uint64, body []byte) error { - switch id { - case 0x1A45DFA3: - return nil - case 0x18538067: - sawSegment = true - return walkSegment(&f, body) - } - return fmt.Errorf("a top level element %#x that is neither the EBML header nor the segment", id) - }) - if err == nil && !sawSegment { - err = fmt.Errorf("no segment") - } - return f, err -} - -func walkSegment(f *webmFilm, body []byte) error { - return ebmlElements(body, func(id uint64, el []byte) error { - f.tail = [2]uint64{f.tail[1], id} - switch id { - case 0x1549A966: // Info - return ebmlElements(el, func(id uint64, v []byte) error { - if id == 0x4489 { - f.durationMs = math.Float64frombits(binary.BigEndian.Uint64(v)) - } - return nil - }) - case 0x1654AE6B: // Tracks - return walkTracks(f, el) - case 0x1F43B675: // Cluster - return walkCluster(f, el) - case 0x1C53BB6B: // Cues - return ebmlElements(el, func(id uint64, _ []byte) error { - if id == 0xBB { - f.cuePoints++ - } - return nil - }) - } - return nil - }) -} - -func walkTracks(f *webmFilm, el []byte) error { - return ebmlElements(el, func(id uint64, entry []byte) error { - return ebmlElements(entry, func(id uint64, v []byte) error { - switch id { - case 0x63A2: - f.codecPrivate = v - case 0xE0: - return ebmlElements(v, func(id uint64, d []byte) error { - switch id { - case 0xB0: - f.width = int(beUint(d)) - case 0xBA: - f.height = int(beUint(d)) - } - return nil - }) - } - return nil - }) - }) -} - -func walkCluster(f *webmFilm, el []byte) error { - var clusterTs int64 - return ebmlElements(el, func(id uint64, v []byte) error { - switch id { - case 0xE7: - clusterTs = int64(beUint(v)) - case 0xA3: - if len(v) < 4 || v[0] != 0x81 { - return fmt.Errorf("a block that is not on track 1") - } - rel := int64(int16(binary.BigEndian.Uint16(v[1:3]))) - f.blocks = append(f.blocks, filmBlock{ts: clusterTs + rel, key: v[3]&0x80 != 0, data: v[4:]}) - default: - return fmt.Errorf("a cluster holds element %#x", id) - } - return nil - }) -} - // av1Frame is what one OBU of a sample says about showing pictures. type av1Frame struct { showExisting bool @@ -300,8 +179,8 @@ func showRuleBroken(blocks []filmBlock) (int, string) { return -1, "" } -func filmTarget(bytes int64, props map[string]string) engine.Target { - return engine.Target{ID: "film", Format: "webm", Sizes: engine.Uniform(1, bytes), Label: true, Properties: props} +func filmTarget(id string, bytes int64, props map[string]string) engine.Target { + return engine.Target{ID: "film", Format: id, Sizes: engine.Uniform(1, bytes), Label: true, Properties: props} } // The stream's one shape: a key frame, its hidden copy, and every other frame @@ -318,17 +197,19 @@ func TestAFilmNeverShowsAKeyFrameASecondTime(t *testing.T) { {"duration": "2s", "keyframe_interval": "1s", "frame_rate": "1"}, {"duration": "40ms", "frame_rate": "25"}, } - for _, props := range cases { - b, _ := filmOne(t, filmTarget(64*1024, props)) - f, err := walkWebM(b) - if err != nil { - t.Fatalf("%v: reading the film back: %v", props, err) - } - if len(f.blocks) == 0 { - t.Fatalf("%v: the film has no frames, so nothing here was checked", props) - } - if i, why := showRuleBroken(f.blocks); i >= 0 { - t.Errorf("%v: frame %d %s - a stream libaom refuses", props, i, why) + for _, id := range filmFormats { + for _, props := range cases { + b, _ := filmOne(t, filmTarget(id, 64*1024, props)) + f, err := walkFilm(id, b) + if err != nil { + t.Fatalf("%s %v: reading the film back: %v", id, props, err) + } + if len(f.blocks) == 0 { + t.Fatalf("%s %v: the film has no frames, so nothing here was checked", id, props) + } + if i, why := showRuleBroken(f.blocks); i >= 0 { + t.Errorf("%s %v: frame %d %s - a stream libaom refuses", id, props, i, why) + } } } } @@ -342,52 +223,63 @@ func TestAFilmHoldsTheFramesTheManifestDeclares(t *testing.T) { {"duration": "59.9s", "frame_rate": "30"}, {"duration": "1h", "frame_rate": "60"}, } - for _, props := range cases { - b, facts := filmOne(t, filmTarget(2*1024*1024, props)) - f, err := walkWebM(b) - if err != nil { - t.Fatalf("%v: reading the film back: %v", props, err) - } - frames, _ := facts["frame_count"].(int64) - keys, _ := facts["keyframe_count"].(int64) - durationMs, _ := facts["duration_ms"].(int64) - fps, _ := facts["frame_rate"].(int) - if frames == 0 || fps == 0 { - t.Fatalf("%v: the manifest declares %d frames at %d a second, so nothing here was checked", props, frames, fps) - } - if int64(len(f.blocks)) != frames { - t.Errorf("%v: the file holds %d frames and the manifest declares %d", props, len(f.blocks), frames) - } - if f.durationMs != float64(durationMs) { - t.Errorf("%v: the file says it lasts %v ms and the manifest %d ms", props, f.durationMs, durationMs) - } - var seenKeys int64 - interval, _ := facts["keyframe_interval_ms"].(int64) - every := interval * int64(fps) / 1000 - for i, blk := range f.blocks { - if blk.key { - seenKeys++ - if every > 0 && int64(i)%every != 0 { - t.Errorf("%v: frame %d is a key frame and the interval puts them every %d frames", props, i, every) - } + for _, id := range filmFormats { + for _, props := range cases { + b, facts := filmOne(t, filmTarget(id, 2*1024*1024, props)) + f, err := walkFilm(id, b) + if err != nil { + t.Fatalf("%s %v: reading the film back: %v", id, props, err) } + filmAgreesWithItsManifest(t, id+" "+fmt.Sprint(props), f, facts) } - if seenKeys != keys || int64(f.cuePoints) != keys { - t.Errorf("%v: %d key frames and %d cue points, and the manifest declares %d key frames", props, seenKeys, f.cuePoints, keys) - } - if w, _ := facts["width"].(int); w != f.width { - t.Errorf("%v: the track is %d px wide and the manifest says %d", props, f.width, w) - } - if h, _ := facts["height"].(int); h != f.height { - t.Errorf("%v: the track is %d px tall and the manifest says %d", props, f.height, h) - } - if f.tail != [2]uint64{0xEC, 0x1C53BB6B} { - t.Errorf("%v: the segment ends in elements %#x and %#x, and it ends in the padding and then the Cues", props, f.tail[0], f.tail[1]) - } - if last := f.blocks[len(f.blocks)-1].ts; last >= int64(durationMs) { - t.Errorf("%v: the last frame starts at %d ms, at or after the film's end at %d ms", props, last, durationMs) + } +} + +// filmAgreesWithItsManifest asks a film read back whether it holds what its +// manifest declares: the frames, their length, the key frames where the +// interval puts them and each in the container's index, the picture's size, +// and the padding where the container keeps it. +func filmAgreesWithItsManifest(t *testing.T, name string, f filmRead, facts map[string]any) { + t.Helper() + frames, _ := facts["frame_count"].(int64) + keys, _ := facts["keyframe_count"].(int64) + durationMs, _ := facts["duration_ms"].(int64) + fps, _ := facts["frame_rate"].(int) + if frames == 0 || fps == 0 || len(f.blocks) == 0 { + t.Fatalf("%s: the manifest declares %d frames at %d a second and the file holds %d, so nothing here was checked", name, frames, fps, len(f.blocks)) + } + if int64(len(f.blocks)) != frames { + t.Errorf("%s: the file holds %d frames and the manifest declares %d", name, len(f.blocks), frames) + } + if f.durationMs != float64(durationMs) { + t.Errorf("%s: the file says it lasts %v ms and the manifest %d ms", name, f.durationMs, durationMs) + } + var seenKeys int64 + interval, _ := facts["keyframe_interval_ms"].(int64) + every := interval * int64(fps) / 1000 + for i, blk := range f.blocks { + if blk.key { + seenKeys++ + if every > 0 && int64(i)%every != 0 { + t.Errorf("%s: frame %d is a key frame and the interval puts them every %d frames", name, i, every) + } } } + if seenKeys != keys || int64(f.index) != keys { + t.Errorf("%s: %d key frames and %d in the index, and the manifest declares %d key frames", name, seenKeys, f.index, keys) + } + if w, _ := facts["width"].(int); w != f.width { + t.Errorf("%s: the track is %d px wide and the manifest says %d", name, f.width, w) + } + if h, _ := facts["height"].(int); h != f.height { + t.Errorf("%s: the track is %d px tall and the manifest says %d", name, f.height, h) + } + if f.ends != "" { + t.Errorf("%s: %s", name, f.ends) + } + if last := f.blocks[len(f.blocks)-1].ts; last >= durationMs { + t.Errorf("%s: the last frame starts at %d ms, at or after the film's end at %d ms", name, last, durationMs) + } } // The level a film declares is the lowest the AV1 specification allows it. @@ -454,23 +346,25 @@ func TestALengthThatDoesNotEndOnAFrameIsRefusedWithTheTwoNearest(t *testing.T) { {map[string]string{"keyframe_interval": "1.05s"}, "keyframe_interval", "Ask for 1s or 1.1s"}, {map[string]string{"duration": "24h", "keyframe_interval": "500ms"}, "keyframe_interval", "Ask for 900ms or longer, or a shorter film"}, // 864ms is not on a frame at 30 fps } - for _, c := range cases { - _, err := engine.Plan([]engine.Target{filmTarget(1024*1024, c.props)}, engine.Options{OutDir: t.TempDir(), Seed: goldenSeed, Command: "test"}) - var refused *format.PropertyValueError - if !errors.As(err, &refused) { - t.Errorf("%v was not refused as a setting it cannot have: %v", c.props, err) - continue - } - // The way out is in the sentence the command line prints, which is - // the reason - a remedy beside it would reach the window alone. - if refused.Key != c.key || !strings.HasSuffix(refused.Error(), c.offered) { - t.Errorf("%v was refused about %q as %q - expected %q ending in %q", c.props, refused.Key, refused.Error(), c.key, c.offered) + for _, id := range filmFormats { + for _, c := range cases { + _, err := engine.Plan([]engine.Target{filmTarget(id, 1024*1024, c.props)}, engine.Options{OutDir: t.TempDir(), Seed: goldenSeed, Command: "test"}) + var refused *format.PropertyValueError + if !errors.As(err, &refused) { + t.Errorf("%s %v was not refused as a setting it cannot have: %v", id, c.props, err) + continue + } + // The way out is in the sentence the command line prints, which + // is the reason - a remedy beside it would reach the window alone. + if refused.Key != c.key || !strings.HasSuffix(refused.Error(), c.offered) { + t.Errorf("%s %v was refused about %q as %q - expected %q ending in %q", id, c.props, refused.Key, refused.Error(), c.key, c.offered) + } } - } - // And a length that does end on a frame is not refused at all. - for _, props := range []map[string]string{{"duration": "1.1s"}, {"duration": "1.125s", "frame_rate": "24"}, {"duration": "40ms", "frame_rate": "25"}} { - if _, err := engine.Plan([]engine.Target{filmTarget(1024*1024, props)}, engine.Options{OutDir: t.TempDir(), Seed: goldenSeed, Command: "test"}); err != nil { - t.Errorf("%v ends on a frame and was refused: %v", props, err) + // And a length that does end on a frame is not refused at all. + for _, props := range []map[string]string{{"duration": "1.1s"}, {"duration": "1.125s", "frame_rate": "24"}, {"duration": "40ms", "frame_rate": "25"}} { + if _, err := engine.Plan([]engine.Target{filmTarget(id, 1024*1024, props)}, engine.Options{OutDir: t.TempDir(), Seed: goldenSeed, Command: "test"}); err != nil { + t.Errorf("%s %v ends on a frame and was refused: %v", id, props, err) + } } } } @@ -492,47 +386,50 @@ func TestAFilmPictureChangesWhereTheManifestSays(t *testing.T) { {"duration": "2s", "change_interval": "1h"}, } moving, tiled := 0, 0 - for _, props := range cases { - b, facts := filmOne(t, filmTarget(2*1024*1024, props)) - if filmTileCount(t, b) > 1 { - tiled++ - } - path := filepath.Join(t.TempDir(), "film.webm") - if err := os.WriteFile(path, b, 0o600); err != nil { - t.Fatal(err) - } - got, res := oracle.PictureChanges(path) - if !res.Available { - t.Skipf("%s is not installed, so no picture was looked at - a skip, not a pass", res.Tool) - } - if res.Err != nil { - t.Errorf("%v: %v", props, res.Err) - continue - } - count, _ := facts["change_count"].(int64) - interval, _ := facts["change_interval_ms"].(int64) - fps, _ := facts["frame_rate"].(int) - if count == 0 || interval == 0 || fps == 0 { - t.Fatalf("%v: the manifest declares %d changes every %d ms at %d frames a second, so nothing here was checked", props, count, interval, fps) - } - want := make([]int64, count) - for c := range want { - want[c] = int64(c) * interval * int64(fps) / 1000 - } - if !slices.Equal(got, want) { - t.Errorf("%v: the decoded picture changes at frames %v and the manifest puts the changes at %v", props, got, want) - } - if count > 1 { - moving++ + for _, id := range filmFormats { + for _, props := range cases { + b, facts := filmOne(t, filmTarget(id, 2*1024*1024, props)) + if filmTileCount(t, id, b) > 1 { + tiled++ + } + path := filepath.Join(t.TempDir(), "film."+id) + if err := os.WriteFile(path, b, 0o600); err != nil { + t.Fatal(err) + } + got, res := oracle.PictureChanges(path) + if !res.Available { + t.Skipf("%s is not installed, so no picture was looked at - a skip, not a pass", res.Tool) + } + if res.Err != nil { + t.Errorf("%s %v: %v", id, props, res.Err) + continue + } + count, _ := facts["change_count"].(int64) + interval, _ := facts["change_interval_ms"].(int64) + fps, _ := facts["frame_rate"].(int) + if count == 0 || interval == 0 || fps == 0 { + t.Fatalf("%s %v: the manifest declares %d changes every %d ms at %d frames a second, so nothing here was checked", id, props, count, interval, fps) + } + want := make([]int64, count) + for c := range want { + want[c] = int64(c) * interval * int64(fps) / 1000 + } + if !slices.Equal(got, want) { + t.Errorf("%s %v: the decoded picture changes at frames %v and the manifest puts the changes at %v", id, props, got, want) + } + if count > 1 { + moving++ + } } } - if moving < 3 { + if moving < 3*len(filmFormats) { t.Fatalf("only %d of the films had a picture that moves, so the guard did not ask its question", moving) } // Asserted, not assumed: a picture cut into tiles is coded a tile at a // time, and a tile kept from an earlier picture is where a picture that - // should move could stand still. - if tiled < 2 { + // should move could stand still - in MP4 also from the first of its two + // passes. + if tiled < 2*len(filmFormats) { t.Fatalf("only %d of the films were cut into tiles, so the guard did not ask about tiles", tiled) } } @@ -556,35 +453,37 @@ func TestEveryFrameOfAFilmSurvivesItsReferenceTool(t *testing.T) { // Wider than one tile can be (grid.go, cutToFit). {"width": "4240", "height": "1000", "duration": "2s"}, } - tiled := 0 - for _, props := range cases { - b, facts := filmOne(t, filmTarget(4*1024*1024, props)) - if filmTileCount(t, b) > 1 { - tiled++ - } - path := filepath.Join(t.TempDir(), "film.webm") - if err := os.WriteFile(path, b, 0o600); err != nil { - t.Fatal(err) - } - res := checker.Check(path) - if !res.Available { - t.Skipf("%s is not installed, so no film was decoded - a skip, not a pass", checker.Name) - } - if res.Err != nil { - t.Errorf("%v: %v", props, res.Err) - continue + tiled := map[string]int{} + for _, id := range filmFormats { + for _, props := range cases { + b, facts := filmOne(t, filmTarget(id, 4*1024*1024, props)) + if filmTileCount(t, id, b) > 1 { + tiled[id]++ + } + path := filepath.Join(t.TempDir(), "film."+id) + if err := os.WriteFile(path, b, 0o600); err != nil { + t.Fatal(err) + } + res := checker.Check(path) + if !res.Available { + t.Skipf("%s is not installed, so no film was decoded - a skip, not a pass", checker.Name) + } + if res.Err != nil { + t.Errorf("%s %v: %v", id, props, res.Err) + continue + } + frames, _ := facts["frame_count"].(int64) + got, ok := oracle.DecodedFrames(res.Output) + if !ok || int64(got) != frames { + t.Errorf("%s %v: libaom decoded %d frames and the manifest declares %d", id, props, got, frames) + } } - frames, _ := facts["frame_count"].(int64) - got, ok := oracle.DecodedFrames(res.Output) - if !ok || int64(got) != frames { - t.Errorf("%v: libaom decoded %d frames and the manifest declares %d", props, got, frames) + // A frame of tiles is the one whose tile_info and tile sizes this tool + // writes itself, so at least one film of each container has to be one. + if tiled[id] == 0 { + t.Fatalf("no %s film was cut into tiles, so libaom was never asked about a frame of tiles", id) } } - // A frame of tiles is the one whose tile_info and tile sizes this tool - // writes itself, so at least one film has to be one. - if tiled == 0 { - t.Fatalf("no film was cut into tiles, so libaom was never asked about a frame of tiles") - } } // A picture larger than any AV1 level describes is refused as a setting, @@ -600,20 +499,22 @@ func TestEveryFrameOfAFilmSurvivesItsReferenceTool(t *testing.T) { // are the four the old bound refused, cheap to plan, and the two longest // sides - not 8192x4352 itself, whose plan codes a sample of 8K pictures. func TestAFilmPictureLargerThanAnyAV1LevelIsRefusedNotBroken(t *testing.T) { - plan := func(w, h string) error { - props := map[string]string{"width": w, "height": h, "duration": "1s"} - _, err := engine.Plan([]engine.Target{filmTarget(32*1024*1024, props)}, engine.Options{OutDir: t.TempDir(), Seed: goldenSeed, Command: "test"}) - return err - } - for _, size := range [][2]string{{"8192", "4353"}, {"8193", "4352"}, {"16384", "2177"}, {"16385", "64"}, {"64", "16385"}} { - var refused *format.PropertyValueError - if err := plan(size[0], size[1]); !errors.As(err, &refused) { - t.Errorf("%sx%s was not refused as a setting: %v", size[0], size[1], err) + for _, id := range filmFormats { + plan := func(w, h string) error { + props := map[string]string{"width": w, "height": h, "duration": "1s"} + _, err := engine.Plan([]engine.Target{filmTarget(id, 32*1024*1024, props)}, engine.Options{OutDir: t.TempDir(), Seed: goldenSeed, Command: "test"}) + return err } - } - for _, size := range [][2]string{{"4096", "2305"}, {"4000", "2359"}, {"4097", "64"}, {"4352", "512"}, {"16384", "64"}, {"64", "16384"}} { - if err := plan(size[0], size[1]); err != nil { - t.Errorf("%sx%s is inside every bound the format declares and was refused: %v", size[0], size[1], err) + for _, size := range [][2]string{{"8192", "4353"}, {"8193", "4352"}, {"16384", "2177"}, {"16385", "64"}, {"64", "16385"}} { + var refused *format.PropertyValueError + if err := plan(size[0], size[1]); !errors.As(err, &refused) { + t.Errorf("%s %sx%s was not refused as a setting: %v", id, size[0], size[1], err) + } + } + for _, size := range [][2]string{{"4096", "2305"}, {"4000", "2359"}, {"4097", "64"}, {"4352", "512"}, {"16384", "64"}, {"64", "16384"}} { + if err := plan(size[0], size[1]); err != nil { + t.Errorf("%s %sx%s is inside every bound the format declares and was refused: %v", id, size[0], size[1], err) + } } } } @@ -641,19 +542,21 @@ func TestAFilmOfANamedSizeKeepsEveryPictureInsideItsReserve(t *testing.T) { {16 << 20, map[string]string{"width": "320", "height": "180", "duration": "2m", "change_interval": "100ms"}}, } tiled := 0 - for _, c := range cases { - b, facts := filmOne(t, filmTarget(c.bytes, c.props)) - if filmTileCount(t, b) > 1 { - tiled++ - } - if changes, _ := facts["change_count"].(int64); changes <= 10 { - t.Fatalf("%v: %d pictures, which planning codes whole, so the sample was not asked", c.props, changes) - } - if int64(len(b)) != c.bytes { - t.Errorf("%v: the film is %d B and %d B were asked for", c.props, len(b), c.bytes) + for _, id := range filmFormats { + for _, c := range cases { + b, facts := filmOne(t, filmTarget(id, c.bytes, c.props)) + if filmTileCount(t, id, b) > 1 { + tiled++ + } + if changes, _ := facts["change_count"].(int64); changes <= 10 { + t.Fatalf("%s %v: %d pictures, which planning codes whole, so the sample was not asked", id, c.props, changes) + } + if int64(len(b)) != c.bytes { + t.Errorf("%s %v: the film is %d B and %d B were asked for", id, c.props, len(b), c.bytes) + } } } - if tiled == 0 { - t.Fatalf("no film was cut into tiles, so no reserve of tiles was asked") + if tiled < len(filmFormats) { + t.Fatalf("%d films were cut into tiles, so no reserve of tiles was asked of each container", tiled) } } diff --git a/internal/guard/filmahead_test.go b/internal/guard/filmahead_test.go index 7ecaf302..af41a92f 100644 --- a/internal/guard/filmahead_test.go +++ b/internal/guard/filmahead_test.go @@ -3,6 +3,7 @@ package guard import ( "bytes" "context" + "fmt" "runtime" "slices" "testing" @@ -88,15 +89,20 @@ func TestAFilmPaintsOnlyWhatChangesAndGetsTheWholePicture(t *testing.T) { // otherwise both runs are the same goroutine and the comparison is of a film // with itself. One film is sized by hand, so planning codes its sample through // the helpers too, and one is chosen to fit. +// +// An MP4 codes its film twice, the second time taking what the first kept +// (video.Pictures.Again), and both passes take helpers, so it is asked too. func TestAFilmCodedBySeveralGoroutinesHasTheBytesOfOne(t *testing.T) { - targets := []engine.Target{ - filmTarget(2<<20, map[string]string{"width": "64", "height": "48", "duration": "40s"}), - filmTarget(1<<20, map[string]string{"duration": "30s", "change_interval": "500ms"}), + var targets []engine.Target + for _, id := range filmFormats { + targets = append(targets, + filmTarget(id, 2<<20, map[string]string{"width": "64", "height": "48", "duration": "40s"}), + filmTarget(id, 1<<20, map[string]string{"duration": "30s", "change_interval": "500ms"})) } tiled := 0 for _, target := range targets { alone, helped := filmUnder(t, 1, target), filmUnder(t, 8, target) - if filmTileCount(t, alone.bytes) > 1 { + if filmTileCount(t, target.Format, alone.bytes) > 1 { tiled++ } if alone.coded != 0 { @@ -112,8 +118,8 @@ func TestAFilmCodedBySeveralGoroutinesHasTheBytesOfOne(t *testing.T) { // Tiles are what helpers code, and a tile one helper coded is taken by // pictures others are waiting for - the film of tiles is the one where // the order of who coded what could show. - if tiled == 0 { - t.Fatalf("neither film was cut into tiles, so helpers coding tiles were never compared") + if tiled < len(filmFormats) { + t.Fatalf("%d films were cut into tiles, so helpers coding tiles were not compared in each container", tiled) } } @@ -146,11 +152,15 @@ func filmUnder(t *testing.T, threads int, target engine.Target) filmRun { // comes up, so most of its work is the first picture and the square's ten // places - and the bar has to move with that too. func TestAFilmsProgressMovesWithItsPicturesNotItsPadding(t *testing.T) { - for _, props := range []map[string]string{ - {"width": "160", "height": "90", "duration": "5m"}, - {"width": "640", "height": "360", "duration": "5m"}, + for _, target := range []engine.Target{ + filmTarget("webm", 32<<20, map[string]string{"width": "160", "height": "90", "duration": "5m"}), + filmTarget("webm", 32<<20, map[string]string{"width": "640", "height": "360", "duration": "5m"}), + // An MP4 codes its pictures before it writes a byte, so its bar is all + // work first - which a bar counting bytes would show standing still. + filmTarget("mp4", 32<<20, map[string]string{"width": "640", "height": "360", "duration": "5m"}), } { - reports, planned := progressOf(t, filmTarget(32<<20, props)) + props := target.Format + " " + fmt.Sprint(target.Properties) + reports, planned := progressOf(t, target) if changes, _ := planned.Properties["change_count"].(int64); changes < 100 || planned.Work <= 0 { t.Fatalf("%v: the film has %d pictures and declares %d of work, so there were no pictures for the bar to move with", props, changes, planned.Work) } @@ -203,34 +213,69 @@ func progressOf(t *testing.T, target engine.Target) ([]engine.Progress, format.P // was taken and pass without asking anything. func TestAFilmStoppedHalfWayLeavesNoHelperRunning(t *testing.T) { defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8)) - target := filmTarget(8<<20, map[string]string{"width": "160", "height": "90", "duration": "10m"}) - dir := t.TempDir() + // Stopped while pictures are being written is stopped once helpers have + // coded since the file's first byte, which is the state asked about rather + // than a number of bytes that stands for it. + writing := func(p engine.Progress, codedSinceFirstByte int64) bool { return codedSinceFirstByte > 8 } + small := map[string]string{"width": "160", "height": "90", "duration": "10m"} + stops := []struct { + format, where string + props map[string]string + now func(p engine.Progress, codedSinceFirstByte int64) bool + }{ + {"webm", "while its pictures are being written", small, writing}, + // An MP4 has two passes, each with helpers of its own: stopped while + // the first codes and nothing of the file is written yet, and while + // the second writes. The second codes only the clock's tiles the first + // had no room to keep, so that film changes ten times a second - six + // thousand tiles of the clock, more than a film keeps. + {"mp4", "while its first pass codes", small, func(p engine.Progress, _ int64) bool { return p.BytesDone == 0 && p.WorkDone > p.WorkTotal/4 }}, + {"mp4", "while its second pass codes", map[string]string{"width": "256", "height": "144", "duration": "10m", "change_interval": "100ms"}, writing}, + } + for _, stop := range stops { + stopFilmHalfWay(t, stop.format+" stopped "+stop.where, filmTarget(stop.format, 64<<20, stop.props), stop.now) + } +} + +// stopFilmHalfWay runs a film, stops it at the first report now says yes to, +// and asks that the run was stopped, that helpers had coded before it was, +// and that none is left running. +func stopFilmHalfWay(t *testing.T, name string, target engine.Target, now func(engine.Progress, int64) bool) { + t.Helper() ctx, cancel := context.WithCancel(context.Background()) defer cancel() - opt := engine.Options{OutDir: dir, Seed: goldenSeed, Command: "test"} + opt := engine.Options{OutDir: t.TempDir(), Seed: goldenSeed, Command: "test"} planned, err := engine.Plan([]engine.Target{target}, opt) if err != nil { t.Fatal(err) } if changes, _ := planned[0].Plan.Properties["change_count"].(int64); changes < 100 { - t.Fatalf("the film has %d pictures, too few to be stopped in the middle of them", changes) + t.Fatalf("%s: the film has %d pictures, too few to be stopped in the middle of them", name, changes) } _, codedBefore := video.Helpers() + atFirstByte := int64(-1) opt.OnProgress = func(p engine.Progress) { - // Some forty pictures in, of six hundred. - if p.BytesDone > 100_000 { + _, coded := video.Helpers() + if p.BytesDone > 0 && atFirstByte < 0 { + atFirstByte = coded + } + since := int64(0) + if atFirstByte >= 0 { + since = coded - atFirstByte + } + if now(p, since) { cancel() } } res, runErr := engine.Run(ctx, planned, opt) running, codedAfter := video.Helpers() if runErr == nil && (res == nil || len(res.Manifest.Files) > 0) { - t.Fatalf("the run finished the film, so it was never stopped half way") + t.Fatalf("%s: the run finished the film, so it was never stopped half way", name) } if codedAfter == codedBefore { - t.Fatalf("no helper coded a picture before the stop, so there was nothing that could have been left running") + t.Fatalf("%s: no helper coded a picture before the stop, so there was nothing that could have been left running", name) } if running != 0 { - t.Errorf("%d helpers are still running after the stopped run returned", running) + t.Errorf("%s: %d helpers are still running after the stopped run returned", name, running) } } diff --git a/internal/guard/filmmp4_test.go b/internal/guard/filmmp4_test.go new file mode 100644 index 00000000..e60ef1d0 --- /dev/null +++ b/internal/guard/filmmp4_test.go @@ -0,0 +1,341 @@ +package guard + +import ( + "bytes" + "encoding/binary" + "fmt" + "testing" + + "github.com/donislawdev/TestingFilesGenerator/internal/format/video" +) + +// An MP4 film read back from its bytes into what the film guards ask of a +// WebM: its frames, which of them are key frames, its length and its picture - +// so every question in film_test.go is asked of both containers +// (docs/MP4-2026-10-07.md section 7). +// +// The reader knows the boxes this tool writes and the sample tables as far as +// finding each sample, and refuses what it does not understand, as the WebM +// reader does. + +// mp4Box is one box: its name and its content. +type mp4Box struct { + name string + body []byte +} + +// mp4Boxes is a run of boxes, each exactly covering the next. +func mp4Boxes(b []byte) ([]mp4Box, error) { + var out []mp4Box + for len(b) > 0 { + if len(b) < 8 { + return nil, fmt.Errorf("%d B left where a box should start", len(b)) + } + size, head := uint64(binary.BigEndian.Uint32(b)), uint64(8) + if size == 1 { + if len(b) < 16 { + return nil, fmt.Errorf("a box with a 64 bit length and no room for it") + } + size, head = binary.BigEndian.Uint64(b[8:]), 16 + } + if size < head || size > uint64(len(b)) { + return nil, fmt.Errorf("the box %q says %d B and %d remain", b[4:8], size, len(b)) + } + out = append(out, mp4Box{name: string(b[4:8]), body: b[head:size]}) + b = b[size:] + } + return out, nil +} + +// mp4Child is the one box called name in b, or an error. +func mp4Child(b []byte, name string) ([]byte, error) { + boxes, err := mp4Boxes(b) + if err != nil { + return nil, err + } + var found [][]byte + for _, x := range boxes { + if x.name == name { + found = append(found, x.body) + } + } + if len(found) != 1 { + return nil, fmt.Errorf("%d %q boxes where a film has one", len(found), name) + } + return found[0], nil +} + +// mp4Path follows the one box of each name down from b. +func mp4Path(b []byte, names ...string) ([]byte, error) { + var err error + for _, n := range names { + if b, err = mp4Child(b, n); err != nil { + return nil, fmt.Errorf("%s: %w", n, err) + } + } + return b, nil +} + +func walkMP4(b []byte) (filmRead, error) { + var f filmRead + top, err := mp4Boxes(b) + if err != nil { + return f, err + } + if len(top) < 4 || top[0].name != "ftyp" || top[1].name != "moov" || top[2].name != "mdat" { + return f, fmt.Errorf("the file does not open with ftyp, moov and mdat") + } + for _, x := range top[3:] { + if x.name != "free" { + f.ends = fmt.Sprintf("a %q box after the media data, where only free boxes go", x.name) + } + } + moov := top[1].body + tkhd, err := mp4Path(moov, "trak", "tkhd") + if err != nil { + return f, err + } + f.width, f.height = int(binary.BigEndian.Uint32(tkhd[76:])>>16), int(binary.BigEndian.Uint32(tkhd[80:])>>16) + mdhd, err := mp4Path(moov, "trak", "mdia", "mdhd") + if err != nil { + return f, err + } + scale, ticks := int64(binary.BigEndian.Uint32(mdhd[12:])), int64(binary.BigEndian.Uint32(mdhd[16:])) + f.durationMs = float64(ticks * 1000 / scale) + stbl, err := mp4Path(moov, "trak", "mdia", "minf", "stbl") + if err != nil { + return f, err + } + stsd, err := mp4Child(stbl, "stsd") + if err != nil { + return f, err + } + // The sample entry: a version and a count, then av01, whose boxes follow + // its 78 bytes of fields. + entry, err := mp4Child(stsd[8:], "av01") + if err != nil { + return f, fmt.Errorf("the sample description holds no AV1 entry: %w", err) + } + if len(entry) < 78 { + return f, fmt.Errorf("the AV1 entry is %d B, shorter than its fields", len(entry)) + } + if f.codecPrivate, err = mp4Child(entry[78:], "av1C"); err != nil { + return f, err + } + starts, lens, err := mp4Samples(stbl) + if err != nil { + return f, err + } + syncs, err := mp4Syncs(stbl) + if err != nil { + return f, err + } + f.index = len(syncs) + for i := range starts { + if starts[i] < 0 || starts[i]+lens[i] > int64(len(b)) { + return f, fmt.Errorf("sample %d lies outside the file", i+1) + } + f.blocks = append(f.blocks, filmBlock{ + ts: (int64(i)*1000 + scale/2) / scale, + key: syncs[int64(i)+1], + data: b[starts[i] : starts[i]+lens[i]], + }) + } + return f, nil +} + +// mp4Table is the entries of one sample table, each size bytes, after head +// bytes that end in their count - refused unless the box is exactly that +// long, so no count a file states is read past the box that states it. +func mp4Table(stbl []byte, name string, head, size int) ([][]byte, error) { + b, err := mp4Child(stbl, name) + if err != nil { + return nil, err + } + if len(b) < head { + return nil, fmt.Errorf("%s is %d B, shorter than its own header", name, len(b)) + } + n := int64(binary.BigEndian.Uint32(b[head-4:])) + if int64(len(b)) != int64(head)+int64(size)*n { + return nil, fmt.Errorf("%s says %d entries and is %d B long", name, n, len(b)) + } + out := make([][]byte, n) + for i := range out { + out[i] = b[head+size*i : head+size*(i+1)] + } + return out, nil +} + +// mp4Samples is where each sample starts in the file and how long it is, +// from stsz, stsc and stco or co64. The runs of stsc are walked once, forward +// with the chunks, because a film of irregular intervals has nearly a run per +// chunk. +func mp4Samples(stbl []byte) (starts, lens []int64, err error) { + sizes, err := mp4Table(stbl, "stsz", 12, 4) + if err != nil { + return nil, nil, err + } + for _, e := range sizes { + lens = append(lens, int64(binary.BigEndian.Uint32(e))) + } + runs, err := mp4Table(stbl, "stsc", 8, 12) + if err != nil { + return nil, nil, err + } + if len(runs) == 0 || binary.BigEndian.Uint32(runs[0]) != 1 { + return nil, nil, fmt.Errorf("stsc does not start at the first chunk") + } + places, err := mp4ChunkPlaces(stbl) + if err != nil { + return nil, nil, err + } + run := 0 + for c, place := range places { + for run+1 < len(runs) && int(binary.BigEndian.Uint32(runs[run+1])) <= c+1 { + run++ + } + for range binary.BigEndian.Uint32(runs[run][4:]) { + if len(starts) == len(lens) { + return nil, nil, fmt.Errorf("the chunks hold more samples than the %d stsz lists", len(lens)) + } + starts = append(starts, place) + place += lens[len(starts)-1] + } + } + if len(starts) != len(lens) { + return nil, nil, fmt.Errorf("the chunks hold %d samples and stsz lists %d", len(starts), len(lens)) + } + return starts, lens, nil +} + +func mp4ChunkPlaces(stbl []byte) ([]int64, error) { + boxes, err := mp4Boxes(stbl) + if err != nil { + return nil, err + } + step, table := 4, "stco" + for _, x := range boxes { + if x.name == "co64" { + step, table = 8, "co64" + } + } + entries, err := mp4Table(stbl, table, 8, step) + if err != nil { + return nil, err + } + places := make([]int64, len(entries)) + for i, e := range entries { + if step == 8 { + places[i] = int64(binary.BigEndian.Uint64(e)) + } else { + places[i] = int64(binary.BigEndian.Uint32(e)) + } + } + return places, nil +} + +// mp4Syncs is the set of sync samples, numbered from one. +func mp4Syncs(stbl []byte) (map[int64]bool, error) { + entries, err := mp4Table(stbl, "stss", 8, 4) + if err != nil { + return nil, err + } + out := map[int64]bool{} + for _, e := range entries { + out[int64(binary.BigEndian.Uint32(e))] = true + } + return out, nil +} + +// An MP4 carries the very frames of the WebM made from the same request. +// +// The pictures and the AV1 stream are one package's for both containers, and +// every guard of the tiles, the levels and the picture's pixels asks a WebM. +// This is what lets those answers stand for MP4 too: asserted here, frame for +// frame and byte for byte, rather than assumed from sharing the code. The +// films are the shapes the stream can take - tiles, a picture changing faster +// than key frames, key frames inside a change, one frame - each with its size +// named so both containers choose the same picture, and without a label, +// which names the format and so would differ. +func TestAnMP4CarriesTheFramesOfTheWebMOfTheSameRequest(t *testing.T) { + cases := []map[string]string{ + {"width": "640", "height": "360"}, + {"width": "64", "height": "48", "duration": "3s", "change_interval": "100ms", "keyframe_interval": "1s"}, + {"width": "160", "height": "90", "duration": "7s", "change_interval": "300ms", "keyframe_interval": "700ms"}, + {"width": "16", "height": "16", "duration": "40ms", "frame_rate": "25"}, + } + tiled := 0 + for _, props := range cases { + read := map[string]filmRead{} + for _, id := range filmFormats { + target := filmTarget(id, 2<<20, props) + target.Label = false + b, _ := filmOne(t, target) + f, err := walkFilm(id, b) + if err != nil { + t.Fatalf("%s %v: reading the film back: %v", id, props, err) + } + read[id] = f + } + webm, mp4 := read["webm"], read["mp4"] + if len(webm.blocks) == 0 || len(webm.blocks) != len(mp4.blocks) { + t.Fatalf("%v: the WebM has %d frames and the MP4 %d", props, len(webm.blocks), len(mp4.blocks)) + } + if !bytes.Equal(webm.codecPrivate, mp4.codecPrivate) { + t.Errorf("%v: the codec configuration differs between the containers", props) + } + for i := range webm.blocks { + w, m := webm.blocks[i], mp4.blocks[i] + if w.key != m.key || !bytes.Equal(w.data, m.data) { + t.Errorf("%v: frame %d differs - key %v and %v, %d B and %d B", props, i, w.key, m.key, len(w.data), len(m.data)) + break + } + } + if tilesOf(t, mp4) > 1 { + tiled++ + } + } + if tiled == 0 { + t.Fatalf("no film was cut into tiles, so the samples of a frame of tiles were never compared") + } +} + +// An MP4's second pass codes no tile its first pass kept. +// +// An MP4 codes its film twice (docs/MP4-2026-10-07.md section 2), and what +// keeps the second pass from costing what the first did is that it takes every +// tile the first one kept (video.Pictures.Again). Taking none makes the same +// bytes in twice the time, which no other guard would see. So a film whose +// clock's tiles all fit in what a film keeps codes exactly the tiles the WebM +// of the same film codes. The films are chosen to fit, so planning codes +// nothing and every tile counted is one writing coded - asserted, along with +// a film of tiles among them. +func TestAnMP4CodesNoTileItsFirstPassKept(t *testing.T) { + cases := []map[string]string{ + {}, + {"duration": "1m", "change_interval": "500ms"}, + } + tiled := 0 + for _, props := range cases { + coded := map[string]int64{} + for _, id := range filmFormats { + target := filmTarget(id, 2<<20, props) + target.Label = false + before := video.Coded() + b, _ := filmOne(t, target) + coded[id] = video.Coded() - before + if id == "mp4" && filmTileCount(t, id, b) > 1 { + tiled++ + } + } + if coded["webm"] == 0 { + t.Fatalf("%v: the WebM coded no tile, so there was nothing a second pass could code again", props) + } + if coded["mp4"] != coded["webm"] { + t.Errorf("%v: the MP4 coded %d tiles and the WebM of the same film %d, so its second pass coded again what its first kept", props, coded["mp4"], coded["webm"]) + } + } + if tiled == 0 { + t.Fatalf("no film was cut into tiles, so the tiles a picture is made of were never counted") + } +} diff --git a/internal/guard/filmtiles_test.go b/internal/guard/filmtiles_test.go index 60d50387..192ef12f 100644 --- a/internal/guard/filmtiles_test.go +++ b/internal/guard/filmtiles_test.go @@ -236,12 +236,21 @@ func frameOBU(sample []byte) ([]byte, int, error) { // filmTileCount is how many tiles the first frame of a film is cut into, read // from the file - for the guards that have to know they asked about a film of // tiles rather than a film of one. -func filmTileCount(t *testing.T, film []byte) int { +func filmTileCount(t *testing.T, id string, film []byte) int { t.Helper() - f, err := walkWebM(film) - if err != nil || len(f.blocks) == 0 { + f, err := walkFilm(id, film) + if err != nil { t.Fatalf("reading the film back: %v", err) } + return tilesOf(t, f) +} + +// tilesOf is how many tiles the first frame of a film read back is cut into. +func tilesOf(t *testing.T, f filmRead) int { + t.Helper() + if len(f.blocks) == 0 { + t.Fatalf("the film holds no frames, so it has no tiles to count") + } frame, before, err := frameOBU(f.blocks[0].data) if err != nil { t.Fatal(err) @@ -311,7 +320,7 @@ func rangeOf(n int64) []int64 { // one frame. func tilesOfFilmHold(t *testing.T, props map[string]string, size int64, changes []int64) (int, int) { t.Helper() - b, facts, seed := filmWithSeed(t, filmTarget(size, props)) + b, facts, seed := filmWithSeed(t, filmTarget("webm", size, props)) f, err := walkWebM(b) if err != nil { t.Fatalf("%v: reading the film back: %v", props, err) diff --git a/internal/guard/filmwebm_test.go b/internal/guard/filmwebm_test.go new file mode 100644 index 00000000..3003c70a --- /dev/null +++ b/internal/guard/filmwebm_test.go @@ -0,0 +1,157 @@ +package guard + +import ( + "encoding/binary" + "fmt" + "math" +) + +// A WebM film read back from its bytes into what the film guards ask +// (film_test.go): its elements and blocks as far as those questions need, and +// no further. What it does not understand it refuses, so a change in the writer +// that this reader cannot follow fails loudly rather than being read as +// something it is not. filmmp4_test.go reads an MP4 into the same shape. + +// ebmlVint reads an element ID (keeping its marker) or a size (without it). +func ebmlVint(b []byte, keepMarker bool) (value uint64, length int, err error) { + if len(b) == 0 { + return 0, 0, fmt.Errorf("the file ends where an element should start") + } + length = 1 + for mask := byte(0x80); length <= 8 && b[0]&mask == 0; mask >>= 1 { + length++ + } + if length > 8 || length > len(b) { + return 0, 0, fmt.Errorf("a variable length number runs past the end") + } + value = uint64(b[0]) + if !keepMarker { + value &= uint64(0xFF >> length) + } + for _, c := range b[1:length] { + value = value<<8 | uint64(c) + } + return value, length, nil +} + +// ebmlElements walks a run of elements, each exactly covering the next, and +// refuses one whose size runs past the run. +func ebmlElements(b []byte, each func(id uint64, body []byte) error) error { + for pos := 0; pos < len(b); { + id, n, err := ebmlVint(b[pos:], true) + if err != nil { + return err + } + size, m, err := ebmlVint(b[pos+n:], false) + if err != nil { + return err + } + start := pos + n + m + if uint64(len(b)-start) < size { + return fmt.Errorf("element %#x at %d says %d B and %d remain", id, pos, size, len(b)-start) + } + if err := each(id, b[start:start+int(size)]); err != nil { + return err + } + pos = start + int(size) + } + return nil +} + +func beUint(b []byte) uint64 { + var v uint64 + for _, c := range b { + v = v<<8 | uint64(c) + } + return v +} + +func walkWebM(b []byte) (filmRead, error) { + var f filmRead + sawSegment := false + err := ebmlElements(b, func(id uint64, body []byte) error { + switch id { + case 0x1A45DFA3: + return nil + case 0x18538067: + sawSegment = true + return walkSegment(&f, body) + } + return fmt.Errorf("a top level element %#x that is neither the EBML header nor the segment", id) + }) + if err == nil && !sawSegment { + err = fmt.Errorf("no segment") + } + if f.tail != [2]uint64{0xEC, 0x1C53BB6B} { + f.ends = fmt.Sprintf("the segment ends in elements %#x and %#x, and it ends in the padding and then the Cues", f.tail[0], f.tail[1]) + } + return f, err +} + +func walkSegment(f *filmRead, body []byte) error { + return ebmlElements(body, func(id uint64, el []byte) error { + f.tail = [2]uint64{f.tail[1], id} + switch id { + case 0x1549A966: // Info + return ebmlElements(el, func(id uint64, v []byte) error { + if id == 0x4489 { + f.durationMs = math.Float64frombits(binary.BigEndian.Uint64(v)) + } + return nil + }) + case 0x1654AE6B: // Tracks + return walkTracks(f, el) + case 0x1F43B675: // Cluster + return walkCluster(f, el) + case 0x1C53BB6B: // Cues + return ebmlElements(el, func(id uint64, _ []byte) error { + if id == 0xBB { + f.index++ + } + return nil + }) + } + return nil + }) +} + +func walkTracks(f *filmRead, el []byte) error { + return ebmlElements(el, func(id uint64, entry []byte) error { + return ebmlElements(entry, func(id uint64, v []byte) error { + switch id { + case 0x63A2: + f.codecPrivate = v + case 0xE0: + return ebmlElements(v, func(id uint64, d []byte) error { + switch id { + case 0xB0: + f.width = int(beUint(d)) + case 0xBA: + f.height = int(beUint(d)) + } + return nil + }) + } + return nil + }) + }) +} + +func walkCluster(f *filmRead, el []byte) error { + var clusterTs int64 + return ebmlElements(el, func(id uint64, v []byte) error { + switch id { + case 0xE7: + clusterTs = int64(beUint(v)) + case 0xA3: + if len(v) < 4 || v[0] != 0x81 { + return fmt.Errorf("a block that is not on track 1") + } + rel := int64(int16(binary.BigEndian.Uint16(v[1:3]))) + f.blocks = append(f.blocks, filmBlock{ts: clusterTs + rel, key: v[3]&0x80 != 0, data: v[4:]}) + default: + return fmt.Errorf("a cluster holds element %#x", id) + } + return nil + }) +} diff --git a/internal/guard/freeboxes_test.go b/internal/guard/freeboxes_test.go new file mode 100644 index 00000000..8296e95f --- /dev/null +++ b/internal/guard/freeboxes_test.go @@ -0,0 +1,94 @@ +package guard + +import ( + "context" + "encoding/binary" + "fmt" + "testing" + + "github.com/donislawdev/TestingFilesGenerator/internal/format/isobmff" +) + +// boxCounter reads a run of boxes as it is written, keeping only their +// headers: how many there are, the shortest, and whether the run ended at a +// box's end. Gigabytes go through it without being held. +type boxCounter struct { + header [isobmff.BoxHeader]byte + have int // header bytes seen of the box being read + left uint64 // content still to come of the box being read + boxes int + shortest uint64 + notFree string + collected int64 +} + +func (c *boxCounter) Write(p []byte) (int, error) { + n := len(p) + c.collected += int64(n) + for len(p) > 0 { + if c.left > 0 { + step := min(uint64(len(p)), c.left) + c.left -= step + p = p[step:] + continue + } + k := copy(c.header[c.have:], p) + c.have += k + p = p[k:] + if c.have < isobmff.BoxHeader { + continue + } + size := uint64(binary.BigEndian.Uint32(c.header[:4])) + if string(c.header[4:]) != "free" { + c.notFree = string(c.header[4:]) + } + if size < isobmff.BoxHeader { + return 0, fmt.Errorf("a box says it is %d B, shorter than its header", size) + } + c.boxes++ + if c.shortest == 0 || size < c.shortest { + c.shortest = size + } + c.left, c.have = size-isobmff.BoxHeader, 0 + } + return n, nil +} + +// Padding of any length at all is carried in free boxes, past what one box's +// four byte length can say. +// +// AVIF, JPEG XL and MP4 pad with free boxes (internal/format/isobmff), and a +// box states its length in four bytes, so padding past two gigabytes is +// several boxes. The split is where it can go wrong without a word: a leftover +// of one to seven bytes is no box at all. The lengths are the ones around the +// split - one box exactly full, then one byte, seven and eight past it - each +// read back box by box as it is written, every byte of it. +func TestFreeBoxesCarryPaddingOfAnyLength(t *testing.T) { + // The largest one box carries: a length of two gigabytes, header and all. + full := int64(1 << 31) + cases := []struct { + total int64 + boxes int + }{ + {isobmff.BoxHeader, 1}, + {full, 1}, + {full + 1, 2}, + {full + 7, 2}, + {full + 8, 2}, + } + for _, c := range cases { + var got boxCounter + if err := isobmff.WritePadding(context.Background(), &got, 7, c.total); err != nil { + t.Fatalf("%d B of padding: %v", c.total, err) + } + if got.collected != c.total || got.left != 0 || got.have != 0 { + t.Errorf("%d B of padding came out as %d B, ending %d B into a box", c.total, got.collected, int(got.left)+got.have) + } + if got.boxes != c.boxes || got.notFree != "" { + t.Errorf("%d B of padding is %d boxes (a %q among them), and it takes %d free boxes", c.total, got.boxes, got.notFree, c.boxes) + } + if got.shortest < isobmff.BoxHeader { + t.Errorf("%d B of padding has a box of %d B", c.total, got.shortest) + } + } +} diff --git a/internal/guard/generatorbytes_test.go b/internal/guard/generatorbytes_test.go index 335dd8ac..d8d46205 100644 --- a/internal/guard/generatorbytes_test.go +++ b/internal/guard/generatorbytes_test.go @@ -175,6 +175,21 @@ func goldenCases() map[string]engine.Target { "webm_wide": {ID: "g", Format: "webm", Sizes: engine.Uniform(1, 524288), Label: true, Properties: map[string]string{"width": "4240", "height": "1000", "duration": "2s"}}, + // MP4, the same films in ISO base media boxes (docs/MP4-2026-10-07.md): + // its stream is the WebM's, which TestAnMP4CarriesTheFramesOfTheWebMOfTheSameRequest + // holds frame for frame, so what these pin is the container - the sample + // tables of one key frame and of several, chunks of one tile and of + // tiles, and a free box a byte longer. + "mp4_64kib": {ID: "g", Format: "mp4", Sizes: engine.Uniform(1, 65536), Label: true}, + "mp4_named": {ID: "g", Format: "mp4", Sizes: engine.Uniform(1, 65536), Label: true, + Properties: map[string]string{"width": "64", "height": "48", "duration": "2s", "keyframe_interval": "1s", "frame_rate": "25"}}, + "mp4_odd_size": {ID: "g", Format: "mp4", Sizes: engine.Uniform(1, 65537), Label: true, + Properties: map[string]string{"width": "64", "height": "48"}}, + "mp4_tiles": {ID: "g", Format: "mp4", Sizes: engine.Uniform(1, 65536), Label: true, + Properties: map[string]string{"width": "640", "height": "360", "duration": "3s"}}, + "mp4_wide": {ID: "g", Format: "mp4", Sizes: engine.Uniform(1, 524288), Label: true, + Properties: map[string]string{"width": "4240", "height": "1000", "duration": "2s"}}, + // An odd size. The free box takes any length at all, so this is the case // that would catch padding that could only step in twos. "jxl_odd_size": {ID: "g", Format: "jxl", Sizes: engine.Uniform(1, 65537), Label: true, diff --git a/internal/guard/layers_test.go b/internal/guard/layers_test.go index f45cb030..2115cf79 100644 --- a/internal/guard/layers_test.go +++ b/internal/guard/layers_test.go @@ -72,6 +72,8 @@ var layer = map[string]int{ "internal/format/wav": 1, "internal/format/video": 1, "internal/format/webm": 1, + "internal/format/mp4": 1, + "internal/format/isobmff": 1, "internal/recipe": 2, "internal/preset": 2, @@ -150,6 +152,7 @@ var sameLayerAllowed = map[string][]string{ "internal/format/jxl", "internal/format/wav", "internal/format/webm", + "internal/format/mp4", }, "internal/format/imagelabel": {"internal/format"}, "internal/format/imagedim": {"internal/format"}, @@ -179,10 +182,11 @@ var sameLayerAllowed = map[string][]string{ "internal/format/targz": {"internal/format", "internal/format/archive"}, "internal/format/tiff": {"internal/format", "internal/format/imagelabel", "internal/format/imagedim"}, "internal/format/webp": {"internal/format", "internal/format/imagelabel", "internal/format/imagedim"}, - "internal/format/avif": {"internal/format", "internal/format/imagelabel", "internal/format/imagedim"}, - "internal/format/jxl": {"internal/format", "internal/format/imagelabel", "internal/format/imagedim"}, + "internal/format/avif": {"internal/format", "internal/format/imagelabel", "internal/format/imagedim", "internal/format/isobmff"}, + "internal/format/jxl": {"internal/format", "internal/format/imagelabel", "internal/format/imagedim", "internal/format/isobmff"}, "internal/format/video": {"internal/format", "internal/format/imagelabel", "internal/format/imagedim"}, "internal/format/webm": {"internal/format", "internal/format/video"}, + "internal/format/mp4": {"internal/format", "internal/format/video", "internal/format/isobmff"}, "internal/format/wav": {"internal/format", "internal/format/imagelabel"}, "internal/preset": {"internal/recipe"}, diff --git a/internal/guard/oracle_test.go b/internal/guard/oracle_test.go index 00218562..2f34a224 100644 --- a/internal/guard/oracle_test.go +++ b/internal/guard/oracle_test.go @@ -172,6 +172,10 @@ var structurallyChecked = map[string]bool{ // the rest, and only reading the container sees the frames that are not // there (docs/WIDEO-2026-10-06.md section 9.5). "webm": true, + // Since 2026-10-07. A movie box whose sample lengths disagree with the + // samples plays as far as a decoder gets, and only the tables read against + // the media data say so (docs/MP4-2026-10-07.md section 5). + "mp4": true, } func TestTheStructuralCheckerCoversEveryFormatItShould(t *testing.T) { diff --git a/internal/guard/parity_test.go b/internal/guard/parity_test.go index 5dddf89f..18c5a7f8 100644 --- a/internal/guard/parity_test.go +++ b/internal/guard/parity_test.go @@ -141,6 +141,7 @@ var reachableFromTheWindow = []string{ "format:txt", "format:wav", "format:webm", + "format:mp4", "format:xml", "format:yaml", "format:toml", @@ -235,6 +236,13 @@ var reachableFromTheWindow = []string{ "property:wav.channels", "property:wav.content", "property:wav.sample_rate", + "property:mp4.change_interval", + "property:mp4.duration", + "property:mp4.frame_rate", + "property:mp4.height", + "property:mp4.keyframe_interval", + "property:mp4.quality", + "property:mp4.width", "property:webm.change_interval", "property:webm.duration", "property:webm.frame_rate", diff --git a/internal/guard/presetbytes_test.go b/internal/guard/presetbytes_test.go index b006a741..038eeaa4 100644 --- a/internal/guard/presetbytes_test.go +++ b/internal/guard/presetbytes_test.go @@ -87,7 +87,11 @@ func TestEjectingAPresetGivesTheBytesItAlwaysGave(t *testing.T) { // than at the longest gav1d could, so the smallest film went from // 3294 B to 3284 B - the same film, a bound ten bytes closer to it // (docs/WEBM-WYDAJNOSC-2026-10-06.md section 10). - {id: "empty-and-minimal", args: preset.Args{}, bytes: 9272, sum: "44600851ef3be72e73df2082c4a653416407efc770ca56bec62de87cf0f41905"}, + // Moved a fourth time on 2026-10-07, the owner's decision of the day + // (docs/MP4-2026-10-07.md section 10): MP4 is a format more, so the + // set has its smallest film, 3312 B. A Changed entry and no major - + // no file any release made changes. + {id: "empty-and-minimal", args: preset.Args{}, bytes: 9574, sum: "3fd628031191f01a2559da100384dcfbecd31bb4ab6629156c0a3ac74f190921"}, {id: "empty-and-minimal", args: preset.Args{"formats": "jpg,png,txt"}, bytes: 1472, sum: "19b12800372395ad3bf5cba6407ca654d7ed51eba3a5bc5436f76a94bc09253c"}, // The preset of unusual file names, measured 2026-09-25 on its first // build: the default, and a format whose extension is a byte longer, diff --git a/internal/guard/said_test.go b/internal/guard/said_test.go index 56e93cbf..2e56e612 100644 --- a/internal/guard/said_test.go +++ b/internal/guard/said_test.go @@ -40,8 +40,9 @@ type saidCall struct { pkg, at string } -// saidID is what an id looks like: the package that says it, then a name. -var saidID = regexp.MustCompile(`^[a-z]+\.[A-Z][A-Za-z0-9]*$`) +// saidID is what an id looks like: the package that says it, then a name. A +// package name may carry digits after its first letter, as mp4 does. +var saidID = regexp.MustCompile(`^[a-z][a-z0-9]*\.[A-Z][A-Za-z0-9]*$`) // saidCalls is every core.Says and core.SaysN in the module, with what is // wrong with how each one is written. diff --git a/internal/guard/testdata/generator-golden.json b/internal/guard/testdata/generator-golden.json index 1627e6cc..f83b5b70 100644 --- a/internal/guard/testdata/generator-golden.json +++ b/internal/guard/testdata/generator-golden.json @@ -356,6 +356,31 @@ "bytes": 524288, "sha256": "270aab596f2a59abe47cf9ab35236964b88dcd1be2ea24247cbce8c72c3c1cd0", "measured_on": "2026-10-07" + }, + "mp4_64kib": { + "bytes": 65536, + "sha256": "5b20b432d9e2287ef0f861331d0ddecea22d8e519351794d95d24abbca13ba65", + "measured_on": "2026-10-07" + }, + "mp4_named": { + "bytes": 65536, + "sha256": "195a999065d1f6f873db51d70b9ffe3d36b5773966d3db037cfacab1e2f76efa", + "measured_on": "2026-10-07" + }, + "mp4_odd_size": { + "bytes": 65537, + "sha256": "c7fd6880cb58015925172ecb80ee0743d32f740a53585f8698daf51d37c3df45", + "measured_on": "2026-10-07" + }, + "mp4_tiles": { + "bytes": 65536, + "sha256": "4ca1c1c7d2d018d527da7d6d067573b65d15629dce931b20e1d95402b868a587", + "measured_on": "2026-10-07" + }, + "mp4_wide": { + "bytes": 524288, + "sha256": "ae5d267ff067caf04aa42e72916f67a89a19584d2ea30445a955202571fc45c3", + "measured_on": "2026-10-07" } }, "remeasured": [ diff --git a/internal/guard/testdata/screens/catalogue.png b/internal/guard/testdata/screens/catalogue.png index 053acaae..e1359cc8 100644 Binary files a/internal/guard/testdata/screens/catalogue.png and b/internal/guard/testdata/screens/catalogue.png differ diff --git a/internal/guard/testdata/screens/catalogue.xml b/internal/guard/testdata/screens/catalogue.xml index 643dacf4..d1bc206b 100644 --- a/internal/guard/testdata/screens/catalogue.xml +++ b/internal/guard/testdata/screens/catalogue.xml @@ -1338,7 +1338,7 @@ - + Archives · 2 @@ -1518,9 +1518,15 @@ - Video · 1 + Video · 2 + + + mp4 + MP4 video + + webm @@ -1531,8 +1537,8 @@ - - + + @@ -1576,7 +1582,7 @@ - + Archives · 1 @@ -1700,13 +1706,25 @@ P lain text + + + Video · 1 + + + + + m + p + 4 + MP4 video + - - + + diff --git a/internal/guard/testdata/screens/generate-menu-hovered.png b/internal/guard/testdata/screens/generate-menu-hovered.png index fa58f96f..379eb023 100644 Binary files a/internal/guard/testdata/screens/generate-menu-hovered.png and b/internal/guard/testdata/screens/generate-menu-hovered.png differ diff --git a/internal/guard/testdata/screens/generate-menu-hovered.xml b/internal/guard/testdata/screens/generate-menu-hovered.xml index 8ad04f44..a67e613e 100644 --- a/internal/guard/testdata/screens/generate-menu-hovered.xml +++ b/internal/guard/testdata/screens/generate-menu-hovered.xml @@ -506,7 +506,7 @@ - + Archives · 2 @@ -686,9 +686,15 @@ - Video · 1 + Video · 2 + + + mp4 + MP4 video + + webm @@ -699,8 +705,8 @@ - - + + diff --git a/internal/guard/testdata/screens/generate-menu-keyed.png b/internal/guard/testdata/screens/generate-menu-keyed.png index a6ebbd72..86562327 100644 Binary files a/internal/guard/testdata/screens/generate-menu-keyed.png and b/internal/guard/testdata/screens/generate-menu-keyed.png differ diff --git a/internal/guard/testdata/screens/generate-menu-keyed.xml b/internal/guard/testdata/screens/generate-menu-keyed.xml index 04157c6a..ef544a38 100644 --- a/internal/guard/testdata/screens/generate-menu-keyed.xml +++ b/internal/guard/testdata/screens/generate-menu-keyed.xml @@ -506,7 +506,7 @@ - + Archives · 2 @@ -686,9 +686,15 @@ - Video · 1 + Video · 2 + + + mp4 + MP4 video + + webm @@ -699,8 +705,8 @@ - - + + diff --git a/internal/guard/testdata/screens/generate-menu.png b/internal/guard/testdata/screens/generate-menu.png index 40e0edc9..a1e69e8e 100644 Binary files a/internal/guard/testdata/screens/generate-menu.png and b/internal/guard/testdata/screens/generate-menu.png differ diff --git a/internal/guard/testdata/screens/generate-menu.xml b/internal/guard/testdata/screens/generate-menu.xml index 36c455f6..73446f3c 100644 --- a/internal/guard/testdata/screens/generate-menu.xml +++ b/internal/guard/testdata/screens/generate-menu.xml @@ -506,7 +506,7 @@ - + Archives · 2 @@ -686,9 +686,15 @@ - Video · 1 + Video · 2 + + + mp4 + MP4 video + + webm @@ -699,8 +705,8 @@ - - + + diff --git a/internal/guard/testdata/screens/preset-menu-setting.png b/internal/guard/testdata/screens/preset-menu-setting.png index 8d54affe..1fd0b090 100644 Binary files a/internal/guard/testdata/screens/preset-menu-setting.png and b/internal/guard/testdata/screens/preset-menu-setting.png differ diff --git a/internal/guard/testdata/screens/preset-menu-setting.xml b/internal/guard/testdata/screens/preset-menu-setting.xml index eef05808..9009391c 100644 --- a/internal/guard/testdata/screens/preset-menu-setting.xml +++ b/internal/guard/testdata/screens/preset-menu-setting.xml @@ -469,7 +469,7 @@ - + Archives · 2 @@ -649,9 +649,15 @@ - Video · 1 + Video · 2 + + + mp4 + MP4 video + + webm @@ -662,8 +668,8 @@ - - + + diff --git a/internal/guard/testdata/screens/preset-menu.png b/internal/guard/testdata/screens/preset-menu.png index a8ad7dd0..ce2a3050 100644 Binary files a/internal/guard/testdata/screens/preset-menu.png and b/internal/guard/testdata/screens/preset-menu.png differ diff --git a/internal/guard/testdata/screens/preset-menu.xml b/internal/guard/testdata/screens/preset-menu.xml index d3841cf2..647ab8df 100644 --- a/internal/guard/testdata/screens/preset-menu.xml +++ b/internal/guard/testdata/screens/preset-menu.xml @@ -370,7 +370,7 @@ - 29 files · 35.1 KB (35 951 B) · 27 formats · will go to /tfg/out + 30 files · 38.3 KB (39 263 B) · 28 formats · will go to /tfg/out diff --git a/internal/guard/testdata/screens/preset.png b/internal/guard/testdata/screens/preset.png index d00c967c..fbf03d8b 100644 Binary files a/internal/guard/testdata/screens/preset.png and b/internal/guard/testdata/screens/preset.png differ diff --git a/internal/guard/testdata/screens/preset.xml b/internal/guard/testdata/screens/preset.xml index 94540179..f8c7b66a 100644 --- a/internal/guard/testdata/screens/preset.xml +++ b/internal/guard/testdata/screens/preset.xml @@ -370,7 +370,7 @@ - 29 files · 35.1 KB (35 951 B) · 27 formats · will go to /tfg/out + 30 files · 38.3 KB (39 263 B) · 28 formats · will go to /tfg/out diff --git a/internal/guard/testdata/screens/recipe-on-a-preset.png b/internal/guard/testdata/screens/recipe-on-a-preset.png index 8e02749d..0f4071cb 100644 Binary files a/internal/guard/testdata/screens/recipe-on-a-preset.png and b/internal/guard/testdata/screens/recipe-on-a-preset.png differ diff --git a/internal/guard/testdata/screens/recipe-on-a-preset.xml b/internal/guard/testdata/screens/recipe-on-a-preset.xml index aaff80df..c82f7581 100644 --- a/internal/guard/testdata/screens/recipe-on-a-preset.xml +++ b/internal/guard/testdata/screens/recipe-on-a-preset.xml @@ -658,7 +658,7 @@ - 30 files · 10.0 MB (10 521 711 B) · 27 formats · will go to /tfg/out + 31 files · 10.0 MB (10 525 023 B) · 28 formats · will go to /tfg/out diff --git a/internal/guard/textformats_test.go b/internal/guard/textformats_test.go index dd81ca39..f3759ace 100644 --- a/internal/guard/textformats_test.go +++ b/internal/guard/textformats_test.go @@ -446,7 +446,7 @@ func TestAnSVGDrawingCarriesRealShapes(t *testing.T) { // already written down cannot, by construction, find what is missing from them. var textFormats = []string{"txt", "md", "log", "csv", "json", "xml", "html", "svg", "yaml", "toml"} -var binaryFormats = []string{"avif", "bmp", "docx", "gif", "ico", "jpg", "jxl", "pdf", "png", "pptx", "targz", "tiff", "wav", "webm", "webp", "xlsx", "zip"} +var binaryFormats = []string{"avif", "bmp", "docx", "gif", "ico", "jpg", "jxl", "mp4", "pdf", "png", "pptx", "targz", "tiff", "wav", "webm", "webp", "xlsx", "zip"} // Every registered format is on exactly one of the two lists above. // diff --git a/internal/gui/parts/filekind.go b/internal/gui/parts/filekind.go index a5708299..492cb77a 100644 --- a/internal/gui/parts/filekind.go +++ b/internal/gui/parts/filekind.go @@ -153,6 +153,7 @@ var fileKinds = map[string]fileKind{ "zip": kindArchive, "wav": kindSound, "webm": kindMoving, + "mp4": kindMoving, "csv": kindWords, "html": kindWords, "json": kindWords, diff --git a/internal/gui/text/locale/registry/en.json b/internal/gui/text/locale/registry/en.json index 902aedda..33f7baf4 100644 --- a/internal/gui/text/locale/registry/en.json +++ b/internal/gui/text/locale/registry/en.json @@ -539,6 +539,11 @@ "hash": "sha256-21262a3cb533", "other": "md" }, + "Choice.format.mp4": { + "description": "One entry of the format list, as the list shows it. A recipe and the command line write the value itself, so a token such as utf-8, a4, sha256 or 644 stays as it is.", + "hash": "sha256-862c4ec62def", + "other": "mp4" + }, "Choice.format.pdf": { "description": "One entry of the format list, as the list shows it. A recipe and the command line write the value itself, so a token such as utf-8, a4, sha256 or 644 stays as it is.", "hash": "sha256-c35b21d6ca39", @@ -1054,6 +1059,41 @@ "hash": "sha256-9b6f8a3338ec", "other": "Which encoding the characters are written in. UTF-16 stores two bytes per character, so a file in it always has an even number of bytes and an odd size is refused." }, + "Detail.format/mp4.change_interval": { + "description": "The sentence under the change_interval setting of the mp4 format, after what it takes.", + "hash": "sha256-13634dfb475a", + "other": "How often the picture changes: the clock in it moves on and the square takes a step. Each change costs a whole picture in bytes. An interval as long as the film or longer keeps one picture throughout." + }, + "Detail.format/mp4.duration": { + "description": "The sentence under the duration setting of the mp4 format, after what it takes.", + "hash": "sha256-adacce8ce234", + "other": "How long the film plays. It has to end on a frame, so at 30 frames a second it goes in steps of 100ms." + }, + "Detail.format/mp4.frame_rate": { + "description": "The sentence under the frame_rate setting of the mp4 format, after what it takes.", + "hash": "sha256-f53baa5f4ccf", + "other": "How many frames the film shows each second." + }, + "Detail.format/mp4.height": { + "description": "The sentence under the height setting of the mp4 format, after what it takes.", + "hash": "sha256-4b336c279584", + "other": "How tall the picture is. Left out, a size is chosen that fits the bytes you asked for." + }, + "Detail.format/mp4.keyframe_interval": { + "description": "The sentence under the keyframe_interval setting of the mp4 format, after what it takes.", + "hash": "sha256-6c717248aaaa", + "other": "How far apart the frames are that a player can start from. Shorter makes skipping through a long film faster and the file bigger, because each of them carries the whole picture twice." + }, + "Detail.format/mp4.quality": { + "description": "The sentence under the quality setting of the mp4 format, after what it takes.", + "hash": "sha256-f6e1c3e1af83", + "other": "How much detail the picture keeps. Higher looks better and takes more of the file, leaving less room for padding." + }, + "Detail.format/mp4.width": { + "description": "The sentence under the width setting of the mp4 format, after what it takes.", + "hash": "sha256-3085ac44f0e0", + "other": "How wide the picture is. Left out, a size is chosen that fits the bytes you asked for." + }, "Detail.format/pdf.author": { "description": "The sentence under the author setting of the pdf format, after what it takes.", "hash": "sha256-33b1ee363d86", @@ -1469,6 +1509,11 @@ "hash": "sha256-0e52f6b9d025", "other": "Markdown" }, + "Format.mp4": { + "description": "What the mp4 format is called, beside its id in a list of formats. The name of a standard stays as it is - only a name that describes the file is translated.", + "hash": "sha256-1f5265d4bfbb", + "other": "MP4 video" + }, "Format.pdf": { "description": "What the pdf format is called, beside its id in a list of formats. The name of a standard stays as it is - only a name that describes the file is translated.", "hash": "sha256-ccf9cd24dc07", @@ -1584,6 +1629,11 @@ "hash": "sha256-269a6c476761", "other": "the encoder holds the whole picture in memory while it works" }, + "Joint.format/mp4.width.height": { + "description": "Why width times height of the mp4 format have a ceiling, at the end of the note under the two, after the word because.", + "hash": "sha256-72e26aa125ce", + "other": "the largest picture any AV1 level describes is 8192 by 4352 pixels" + }, "Joint.format/png.width.height": { "description": "Why width times height of the png format have a ceiling, at the end of the note under the two, after the word because.", "hash": "sha256-0bed7aa664d8", diff --git a/internal/gui/text/locale/registry/pl.json b/internal/gui/text/locale/registry/pl.json index 61320d25..5144d91f 100644 --- a/internal/gui/text/locale/registry/pl.json +++ b/internal/gui/text/locale/registry/pl.json @@ -107,6 +107,7 @@ "Choice.format.jxl": { "hash": "sha256-87953914d2ad", "other": "jxl" }, "Choice.format.log": { "hash": "sha256-836ff184e7b4", "other": "log" }, "Choice.format.md": { "hash": "sha256-21262a3cb533", "other": "md" }, + "Choice.format.mp4": { "hash": "sha256-862c4ec62def", "other": "mp4" }, "Choice.format.pdf": { "hash": "sha256-c35b21d6ca39", "other": "pdf" }, "Choice.format.png": { "hash": "sha256-8f8cbb7dcf46", "other": "png" }, "Choice.format.pptx": { "hash": "sha256-1512e97a4b84", "other": "pptx" }, @@ -210,6 +211,13 @@ "Detail.format/log.timestamps": { "hash": "sha256-b7c69e2424bc", "other": "Czy każdy wpis dzieje się później niż poprzedni. Stałe stawiają każdy wpis w tej samej chwili, tak jak ten format robił, zanim umiał iść naprzód." }, "Detail.format/md.bom": { "hash": "sha256-a2d6a88583d6", "other": "Czy plik zaczyna się od znacznika kolejności bajtów (BOM). Czytnik, który musi zgadywać kodowanie, go potrzebuje, a czytnik, który się go nie spodziewa, pokazuje go jako obce znaki na początku pliku." }, "Detail.format/md.encoding": { "hash": "sha256-9b6f8a3338ec", "other": "W jakim kodowaniu zapisane są znaki. UTF-16 zapisuje dwa bajty na znak, więc plik w nim ma zawsze parzystą liczbę bajtów, a nieparzysty rozmiar jest odrzucany." }, + "Detail.format/mp4.change_interval": { "hash": "sha256-13634dfb475a", "other": "Jak często zmienia się obraz: zegar w nim idzie dalej, a kwadrat robi krok. Każda zmiana kosztuje cały obraz w bajtach, a odstęp równy długości filmu albo dłuższy zostawia jeden obraz przez cały film." }, + "Detail.format/mp4.duration": { "hash": "sha256-adacce8ce234", "other": "Jak długo trwa film. Musi kończyć się na klatce, więc przy 30 klatkach na sekundę idzie co 100ms." }, + "Detail.format/mp4.frame_rate": { "hash": "sha256-f53baa5f4ccf", "other": "Ile klatek film pokazuje w każdej sekundzie." }, + "Detail.format/mp4.height": { "hash": "sha256-4b336c279584", "other": "Jak wysoki jest obraz. Gdy pole zostaje puste, wybierany jest rozmiar, który mieści się w zamówionych bajtach." }, + "Detail.format/mp4.keyframe_interval": { "hash": "sha256-6c717248aaaa", "other": "Jak daleko od siebie są klatki, od których odtwarzacz może zacząć. Krótszy odstęp przyspiesza przewijanie długiego filmu i powiększa plik, bo każda z nich niesie cały obraz dwa razy." }, + "Detail.format/mp4.quality": { "hash": "sha256-f6e1c3e1af83", "other": "Ile szczegółów zachowuje obraz. Wyższa wartość wygląda lepiej i zajmuje więcej pliku, zostawiając mniej miejsca na dopełnienie." }, + "Detail.format/mp4.width": { "hash": "sha256-3085ac44f0e0", "other": "Jak szeroki jest obraz. Gdy pole zostaje puste, wybierany jest rozmiar, który mieści się w zamówionych bajtach." }, "Detail.format/pdf.author": { "hash": "sha256-33b1ee363d86", "other": "Autor we właściwościach dokumentu. Gdy pole zostaje puste, dokument nie podaje żadnego." }, "Detail.format/pdf.created": { "hash": "sha256-76abcd78cc63", "other": "Kiedy według dokumentu powstał. Czas zapisany bez strefy zostaje bez niej, a none pomija datę." }, "Detail.format/pdf.creator": { "hash": "sha256-57ed4066a447", "other": "Program, w którym według dokumentu go napisano, na przykład Microsoft Word. Gdy pole zostaje puste, dokument nie podaje żadnego." }, @@ -293,6 +301,7 @@ "Format.jxl": { "hash": "sha256-5dab1bf80f83", "other": "JPEG XL" }, "Format.log": { "hash": "sha256-b6ef69eb3499", "other": "Log serwera i aplikacji" }, "Format.md": { "hash": "sha256-0e52f6b9d025", "other": "Markdown" }, + "Format.mp4": { "hash": "sha256-1f5265d4bfbb", "other": "Wideo MP4" }, "Format.pdf": { "hash": "sha256-ccf9cd24dc07", "other": "Portable Document Format" }, "Format.png": { "hash": "sha256-016d63e71b45", "other": "Portable Network Graphics" }, "Format.pptx": { "hash": "sha256-33f651fb2245", "other": "PowerPoint (Office Open XML)" }, @@ -316,6 +325,7 @@ "Joint.format/gif.width.height": { "hash": "sha256-0bed7aa664d8", "other": "obraz jest trzymany w pamięci podczas kodowania" }, "Joint.format/jpg.width.height": { "hash": "sha256-0bed7aa664d8", "other": "obraz jest trzymany w pamięci podczas kodowania" }, "Joint.format/jxl.width.height": { "hash": "sha256-269a6c476761", "other": "koder trzyma cały obraz w pamięci podczas pracy" }, + "Joint.format/mp4.width.height": { "hash": "sha256-72e26aa125ce", "other": "największy obraz, jaki opisuje którykolwiek poziom AV1, ma 8192 na 4352 piksele" }, "Joint.format/png.width.height": { "hash": "sha256-0bed7aa664d8", "other": "obraz jest trzymany w pamięci podczas kodowania" }, "Joint.format/webm.width.height": { "hash": "sha256-72e26aa125ce", "other": "największy obraz, jaki opisuje którykolwiek poziom AV1, ma 8192 na 4352 piksele" }, "Joint.format/xlsx.rows.columns": { "hash": "sha256-e1ef283f3c5d", "other": "arkusz powstaje w pamięci, zanim zostanie spakowany" }, diff --git a/internal/gui/text/locale/said/en.json b/internal/gui/text/locale/said/en.json index 860694f9..12eb7e10 100644 --- a/internal/gui/text/locale/said/en.json +++ b/internal/gui/text/locale/said/en.json @@ -1796,6 +1796,11 @@ "hash": "sha256-bb90fafec850", "other": "{{.Path}} cannot be read as a manifest: {{.Detail}}" }, + "mp4.MinimumReasonPictures": { + "description": "Said by internal/format/mp4 - a refusal under the box it is about or at the foot of the form, or a note after a run. Carries {{.Duration}}, {{.FPS}}, {{.Interval}}, {{.Width}}, {{.Height}}, {{.Change}}, {{.Bytes}}, {{.Free}}, written the way the program writes them - keep each spelled exactly that way.", + "hash": "sha256-2d553380ec9d", + "other": "a film of {{.Duration}} at {{.FPS}} frames a second with a key frame every {{.Interval}} and a new {{.Width}}x{{.Height}} picture every {{.Change}} takes {{.Bytes}} B, and the file always carries a free box, which costs {{.Free}} B even when it holds nothing" + }, "pdf.Carrying": { "description": "Said by internal/format/pdf - a refusal under the box it is about or at the foot of the form, or a note after a run. Carries {{.What}}, written the way the program writes them - keep each spelled exactly that way.", "hash": "sha256-83e9af0cb805", @@ -3141,8 +3146,18 @@ "hash": "sha256-a53588601259", "other": "use one of: {{.Known}}" }, + "video.AskForBOrMoreFilmPictures": { + "description": "Said by internal/format/video - a refusal under the box it is about or at the foot of the form, or a note after a run. Carries {{.Floor}}, written the way the program writes them - keep each spelled exactly that way.", + "hash": "sha256-faee56eaa68b", + "other": "Ask for {{.Floor}} B or more, or a shorter film, fewer frames a second, key frames further apart or a new picture less often" + }, + "video.AskForBOrMorePictures": { + "description": "Said by internal/format/video - a refusal under the box it is about or at the foot of the form, or a note after a run. Carries {{.Floor}}, written the way the program writes them - keep each spelled exactly that way.", + "hash": "sha256-504e7380dc2f", + "other": "Ask for {{.Floor}} B or more, or a shorter film, fewer frames a second, key frames further apart, a new picture less often or a smaller picture" + }, "video.NoClock": { - "description": "Said by internal/format/webm - a refusal under the box it is about or at the foot of the form, or a note after a run. Carries {{.Width}}, {{.Height}}, written the way the program writes them - keep each spelled exactly that way.", + "description": "Said by internal/format/video - a refusal under the box it is about or at the foot of the form, or a note after a run. Carries {{.Width}}, {{.Height}}, written the way the program writes them - keep each spelled exactly that way.", "hash": "sha256-9fb32f58b247", "other": "The picture is {{.Width}}x{{.Height}} and the clock needs more room, so this film shows no clock. The square still steps across it where it has room to move." }, @@ -3171,16 +3186,6 @@ "hash": "sha256-b8bfddcdab7d", "other": ", or drop the label" }, - "webm.AskForBOrMoreFilmPictures": { - "description": "Said by internal/format/webm - a refusal under the box it is about or at the foot of the form, or a note after a run. Carries {{.Floor}}, written the way the program writes them - keep each spelled exactly that way.", - "hash": "sha256-faee56eaa68b", - "other": "Ask for {{.Floor}} B or more, or a shorter film, fewer frames a second, key frames further apart or a new picture less often" - }, - "webm.AskForBOrMorePictures": { - "description": "Said by internal/format/webm - a refusal under the box it is about or at the foot of the form, or a note after a run. Carries {{.Floor}}, written the way the program writes them - keep each spelled exactly that way.", - "hash": "sha256-504e7380dc2f", - "other": "Ask for {{.Floor}} B or more, or a shorter film, fewer frames a second, key frames further apart, a new picture less often or a smaller picture" - }, "webm.MinimumReasonPictures": { "description": "Said by internal/format/webm - a refusal under the box it is about or at the foot of the form, or a note after a run. Carries {{.Duration}}, {{.FPS}}, {{.Interval}}, {{.Width}}, {{.Height}}, {{.Change}}, {{.Bytes}}, {{.Void}}, written the way the program writes them - keep each spelled exactly that way.", "hash": "sha256-613f54fd8e25", diff --git a/internal/gui/text/locale/said/pl.json b/internal/gui/text/locale/said/pl.json index 77069513..d5dff26d 100644 --- a/internal/gui/text/locale/said/pl.json +++ b/internal/gui/text/locale/said/pl.json @@ -357,6 +357,7 @@ "manifest.RunInstructionsIsAndItCan": { "hash": "sha256-1c92a916a2de", "other": "run.instructions to {{.Name}}, a może to być tylko nazwa pliku obok manifestu kończąca się na {{.InstructionsSuffix}}. To narzędzie usuwa ten plik razem z manifestem, więc nie wykona takiego, który wskazuje cokolwiek innego. Użyj manifestu, który przebieg naprawdę zapisał, albo usuń ten klucz" }, "manifest.TooLarge": { "hash": "sha256-e12fe7ac5071", "other": "{{.Path}} ma {{.Bytes}} B, a limit to {{.Limit}} B. Manifest jest wczytywany do pamięci, żeby porównać go z katalogiem, więc nieograniczony byłby sposobem na jej wyczerpanie. Sprawdź, czy to manifest zapisany przez to narzędzie, albo podziel przebieg, który opisuje" }, "manifest.Unreadable": { "hash": "sha256-bb90fafec850", "other": "{{.Path}} nie daje się odczytać jako manifest: {{.Detail}}" }, + "mp4.MinimumReasonPictures": { "hash": "sha256-2d553380ec9d", "other": "film o długości {{.Duration}} przy {{.FPS}} klatkach na sekundę z klatką kluczową co {{.Interval}} i nowym obrazem {{.Width}}x{{.Height}} co {{.Change}} zajmuje {{.Bytes}} B, a plik zawsze niesie pudełko free, które kosztuje {{.Free}} B nawet puste" }, "pdf.Carrying": { "hash": "sha256-83e9af0cb805", "other": " z {{.What}}" }, "pdf.ChooseOneSize": { "hash": "sha256-61da6826cc02", "other": "wybierz jeden rozmiar, na przykład a4" }, "pdf.ChooseOneWay": { "hash": "sha256-5f59b0be4c6d", "other": "wybierz {{.Portrait}} albo {{.Landscape}}" }, @@ -625,14 +626,14 @@ "tool.ThereIsNoToolCalled": { "hash": "sha256-151e818f434e", "other": "nie ma narzędzia o nazwie {{.ID}}" }, "tool.ThisBuildHasNoTools": { "hash": "sha256-42b26a93974c", "other": "ta wersja nie ma narzędzi" }, "tool.UseOneOf": { "hash": "sha256-a53588601259", "other": "użyj jednego z: {{.Known}}" }, + "video.AskForBOrMoreFilmPictures": { "hash": "sha256-faee56eaa68b", "other": "Poproś o {{.Floor}} B albo więcej albo o krótszy film, mniej klatek na sekundę, rzadsze klatki kluczowe albo rzadszą zmianę obrazu" }, + "video.AskForBOrMorePictures": { "hash": "sha256-504e7380dc2f", "other": "Poproś o {{.Floor}} B albo więcej albo o krótszy film, mniej klatek na sekundę, rzadsze klatki kluczowe, rzadszą zmianę obrazu albo mniejszy obraz" }, "video.NoClock": { "hash": "sha256-9fb32f58b247", "other": "Obraz ma {{.Width}}x{{.Height}} i zegar potrzebuje więcej miejsca, więc ten film nie pokazuje zegara. Kwadrat dalej przesuwa się po nim tam, gdzie ma miejsce na ruch." }, "video.NotOnAFrame": { "hash": "sha256-4471642f4d67", "other": "przy {{.FPS}} klatkach na sekundę długość musi kończyć się na klatce, a przy tej szybkości długości idą co {{.Step}}. Poproś o {{.Below}} albo {{.Above}}" }, "video.NotOnAFrameShortest": { "hash": "sha256-e6b01f8c0aeb", "other": "przy {{.FPS}} klatkach na sekundę długość musi kończyć się na klatce, a przy tej szybkości długości idą co {{.Step}}. Poproś o {{.Above}}, najkrótszy film przy tej szybkości" }, "video.TooManyChanges": { "hash": "sha256-79ad4cf6e19b", "other": "liczba obrazów w filmie o długości {{.Duration}} z nowym obrazem co {{.Interval}} to {{.Pictures}}, a film może pokazać najwyżej {{.Most}}. Poproś o {{.Shortest}} albo dłużej albo o krótszy film" }, "video.TooManyKeyFrames": { "hash": "sha256-fab3e045a284", "other": "film o długości {{.Duration}} z klatką kluczową co {{.Interval}} ma ich {{.Keys}}, a film może mieć najwyżej {{.Most}}. Poproś o {{.Shortest}} albo dłużej albo o krótszy film" }, "wav.DropTheLabel": { "hash": "sha256-b8bfddcdab7d", "other": " albo usuń etykietę" }, - "webm.AskForBOrMoreFilmPictures": { "hash": "sha256-faee56eaa68b", "other": "Poproś o {{.Floor}} B albo więcej albo o krótszy film, mniej klatek na sekundę, rzadsze klatki kluczowe albo rzadszą zmianę obrazu" }, - "webm.AskForBOrMorePictures": { "hash": "sha256-504e7380dc2f", "other": "Poproś o {{.Floor}} B albo więcej albo o krótszy film, mniej klatek na sekundę, rzadsze klatki kluczowe, rzadszą zmianę obrazu albo mniejszy obraz" }, "webm.MinimumReasonPictures": { "hash": "sha256-613f54fd8e25", "other": "film o długości {{.Duration}} przy {{.FPS}} klatkach na sekundę z klatką kluczową co {{.Interval}} i nowym obrazem {{.Width}}x{{.Height}} co {{.Change}} zajmuje {{.Bytes}} B, a plik zawsze niesie element Void, który kosztuje {{.Void}} B nawet pusty" }, "xlsx.Columns": { "hash": "sha256-5a53b3c96cb2", "one": "{{.Count}} kolumnę", "few": "{{.Count}} kolumny", "many": "{{.Count}} kolumn", "other": "{{.Count}} kolumny" }, "xlsx.JointFewer": { "hash": "sha256-15c3bae806db", "other": "{{.Why}}. Poproś o mniej wierszy albo mniej kolumn" }, diff --git a/internal/oracle/oracle.go b/internal/oracle/oracle.go index e73e169f..4e53300b 100644 --- a/internal/oracle/oracle.go +++ b/internal/oracle/oracle.go @@ -549,7 +549,11 @@ func StrictKnows(formatID string) bool { // The first film, from the day it arrived (2026-10-06). The decoder // says whether the frames that are there play, and only the container // says whether they are all there. - "webm": + "webm", + // The second, from the day it arrived (2026-10-07). The sample tables + // say how long every sample is, and only reading them against the + // media data says the tables and the samples agree. + "mp4": return true } return false diff --git a/internal/oracle/strict.py b/internal/oracle/strict.py index 9918c002..3a358a8a 100644 --- a/internal/oracle/strict.py +++ b/internal/oracle/strict.py @@ -1910,7 +1910,7 @@ def av1_frames(sample): fail(f"an OBU of type {kind} says {size} B and its sample has {len(payload)}") at += 1 + used + size if kind == 2: - fail("a temporal delimiter inside a block, which the AV1 mapping of Matroska removes") + fail("a temporal delimiter inside a sample, which the AV1 mappings of Matroska and of ISO base media both leave out") if kind in (3, 6): # Every field read here sits in the first three bytes. bits = int.from_bytes(payload[:3].ljust(3, b"\0"), "big") @@ -2017,12 +2017,149 @@ def check_webm(data): ok(f"{len(blocks)} frames, {len(keys)} key frames, every one in the Cues, Void before them") +def iso_child(data, start, end, name, depth="the movie box"): + """The one box called name between start and end.""" + found = [(b, e) for n, b, e in iso_boxes(data, start, end) if n == name] + if len(found) != 1: + fail(f"{depth} holds {len(found)} {name.decode('latin1')!r} boxes and a film has one") + return found[0] + + +def u32(data, at): + return struct.unpack(">I", data[at:at + 4])[0] + + +def mp4_entries(data, tables, name, head, size): + """The entries of one sample table, each size bytes, after head bytes that + end in their count - refused unless the box is exactly that long, so no + count a file states is read past the box that states it.""" + b, e = tables[name] + if e - b < head: + fail(f"{name.decode()} is {e - b} B, shorter than its own header") + count = u32(data, b + head - 4) + if e - b != head + size * count: + fail(f"{name.decode()} says {count} entries and is {e - b} B long") + return [data[b + head + size * i:b + head + size * (i + 1)] for i in range(count)] + + +def mp4_tables(data, stbl): + """The sample tables, read into the length, the place and the dependency + of every sample, and the sync samples.""" + tables = {n: (b, e) for n, b, e in iso_boxes(data, *stbl)} + for name in (b"stsd", b"stts", b"stss", b"sdtp", b"stsc", b"stsz"): + if name not in tables: + fail(f"the sample tables have no {name.decode()!r}, which this generator always writes") + b, e = tables[b"stsz"] + if e - b < 12 or u32(data, b + 4) != 0: + fail("stsz gives every sample one length, and this generator lists each") + lengths = [int.from_bytes(x, "big") for x in mp4_entries(data, tables, b"stsz", 12, 4)] + count = len(lengths) + + runs = [(int.from_bytes(x[:4], "big"), int.from_bytes(x[4:], "big")) for x in mp4_entries(data, tables, b"stts", 8, 8)] + if sum(n for n, _ in runs) != count or any(d != 1 for _, d in runs): + fail(f"stts covers {sum(n for n, _ in runs)} samples, not each of the {count} one tick long") + + wide = b"co64" in tables + if wide == (b"stco" in tables): + fail("the sample tables carry both stco and co64, or neither") + places = [int.from_bytes(x, "big") for x in mp4_entries(data, tables, b"co64" if wide else b"stco", 8, 8 if wide else 4)] + + chunk_runs = [(int.from_bytes(x[:4], "big"), int.from_bytes(x[4:8], "big")) for x in mp4_entries(data, tables, b"stsc", 8, 12)] + if not chunk_runs or chunk_runs[0][0] != 1: + fail("stsc does not start at the first chunk") + if any(a[0] >= b_[0] for a, b_ in zip(chunk_runs, chunk_runs[1:])): + fail("stsc lists its runs out of order") + # One forward walk over the runs, not a search of them for every chunk - + # a film of irregular intervals has nearly a run per chunk. + starts, run = [], 0 + for c, at in enumerate(places): + while run + 1 < len(chunk_runs) and chunk_runs[run + 1][0] <= c + 1: + run += 1 + for _ in range(chunk_runs[run][1]): + if len(starts) == count: + fail(f"the chunks hold more samples than the {count} stsz lists") + starts.append(at) + at += lengths[len(starts) - 1] + if len(starts) != count: + fail(f"the chunks hold {len(starts)} samples and stsz lists {count}") + + b, e = tables[b"sdtp"] + if e - b != 4 + count: + fail(f"sdtp is {e - b - 4} B for {count} samples") + depends = [data[b + 4 + i] >> 4 & 3 for i in range(count)] + syncs = [int.from_bytes(x, "big") for x in mp4_entries(data, tables, b"stss", 8, 4)] + return lengths, starts, depends, syncs + + +def check_mp4(data): + """An MP4 film as this generator writes it, read without its code. + + The questions a film can get wrong while every frame still plays + (docs/MP4-2026-10-07.md): the boxes cover the file with the free boxes + last, the sample tables agree with the media data byte for byte - the + samples follow one another from its first byte to its last - every sync + sample opens with the sequence header and a shown key frame and every key + frame is a sync sample (AV1 binding section 2.4), sdtp says which samples + depend on none, and nothing shows a picture that may not be shown again + (docs/WIDEO-2026-10-06.md section 9.2). + """ + top = list(iso_boxes(data, 0, len(data))) + names = [n for n, _, _ in top] + if names[:3] != [b"ftyp", b"moov", b"mdat"] or any(n != b"free" for n in names[3:]) or len(names) < 4: + fail(f"the file is {[n.decode('latin1') for n in names]} and this generator writes ftyp, moov, mdat, then free boxes") + _, fb, fe = top[0] + brands = [data[at:at + 4] for at in range(fb + 8, fe, 4)] + if b"av01" not in brands: + fail("av01 is not among the compatible brands, which the AV1 binding requires (section 2.1)") + _, mb, me = top[2] + + trak = iso_child(data, top[1][1], top[1][2], b"trak") + mdia = iso_child(data, *trak, b"mdia", "the track") + minf = iso_child(data, *mdia, b"minf", "the media box") + stbl = iso_child(data, *minf, b"stbl", "the media information") + lengths, starts, depends, syncs = mp4_tables(data, stbl) + if not lengths: + fail("the film holds no frames") + + at = mb + for i, (start, length) in enumerate(zip(starts, lengths)): + if start != at: + fail(f"sample {i + 1} starts at {start} and the one before it ended at {at}") + at += length + if at != me: + fail(f"the samples end at {at} and the media data at {me}") + + showable = [False] * 8 + keys = [] + for i, (start, length) in enumerate(zip(starts, lengths)): + sample = data[start:start + length] + frames = av1_frames(sample) + if any(f[0] == "fill" and f[1] == 0xFF for f in frames): + keys.append(i + 1) + if sample[0] >> 3 & 0xF != 1 or frames[0] != ("fill", 0xFF, False): + fail(f"sample {i + 1} is a key frame without the sequence header first") + intra = any(f[0] == "fill" for f in frames) + if depends[i] != (2 if intra else 1): + fail(f"sdtp says sample {i + 1} depends {'on others' if depends[i] == 1 else 'on none'} and it {'codes' if intra else 'shows'} a picture") + for frame in frames: + if frame[0] == "show": + if not showable[frame[1]]: + fail(f"sample {i + 1} shows slot {frame[1]}, which holds nothing that may be shown again") + else: + for slot in range(8): + if frame[1] >> slot & 1: + showable[slot] = frame[2] + if keys != syncs: + fail(f"stss names {len(syncs)} sync samples and the film has {len(keys)} key frames, not the same ones") + ok(f"{len(lengths)} frames, {len(keys)} key frames, every one a sync sample, the tables agree with the media data") + + CHECKS = {"png": check_png, "wav": check_wav, "pdf": check_pdf, "zip": check_zip, "log": check_log, "csv": check_csv, "json": check_json, "xml": check_xml, "svg": check_svg, "html": check_html, "targz": check_targz, "bmp": check_bmp, "gif": check_gif, "ico": check_ico, "jpg": check_jpg, "tiff": check_tiff, "webp": check_webp, "avif": check_avif, "jxl": check_jxl, - "webm": check_webm, + "webm": check_webm, "mp4": check_mp4, "docx": check_docx, "xlsx": check_xlsx, "pptx": check_pptx, "txt": check_txt, "md": check_md, "yaml": check_yaml, "toml": check_toml} diff --git a/web/assets/social-preview.png b/web/assets/social-preview.png index b777e3f1..5d38d8e0 100644 Binary files a/web/assets/social-preview.png and b/web/assets/social-preview.png differ diff --git a/web/public/ar/corrupt-test-files/index.html b/web/public/ar/corrupt-test-files/index.html index 5d955a1f..fcad1a99 100644 --- a/web/public/ar/corrupt-test-files/index.html +++ b/web/public/ar/corrupt-test-files/index.html @@ -198,7 +198,7 @@

الملف التالف يبقى بالحجم الذي طلبته

tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

تُكتب الإعدادات بعد النقطتين. يمكن تكرار الخيار، وتُطبَّق أنواع التلف بالترتيب الذي تكتبه. ويعمل مع - كل واحدة من 27 صيغة. + كل واحدة من 28 صيغة.

diff --git a/web/public/ar/faq/index.html b/web/public/ar/faq/index.html index 35b84e43..03c29826 100644 --- a/web/public/ar/faq/index.html +++ b/web/public/ar/faq/index.html @@ -205,7 +205,7 @@

الأسئلة الشائعة

ما الصيغ القادمة؟

-

7z وmp3 وmp4. تعمل اليوم 27 صيغة من البداية إلى النهاية.

+

7z وmp3 وmp4. تعمل اليوم 28 صيغة من البداية إلى النهاية.

@@ -276,7 +276,7 @@

الأسئلة الشائعة

{ "@type": "Question", "name": "ما الصيغ القادمة؟", - "acceptedAnswer": { "@type": "Answer", "text": "7z وmp3 وmp4. تعمل اليوم 27 صيغة من البداية إلى النهاية." } + "acceptedAnswer": { "@type": "Answer", "text": "7z وmp3 وmp4. تعمل اليوم 28 صيغة من البداية إلى النهاية." } }, { "@type": "Question", diff --git a/web/public/ar/formats/index.html b/web/public/ar/formats/index.html index d399f72f..6f55a2b5 100644 --- a/web/public/ar/formats/index.html +++ b/web/public/ar/formats/index.html @@ -4,8 +4,8 @@ -27 صيغة ملف مدعومة - PDF وDOCX وPNG وZIP وغيرها - +28 صيغة ملف مدعومة - PDF وDOCX وPNG وZIP وغيرها + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
-

27 صيغة ملف، كلٌّ منها يُولَّد بحجم دقيق

+

28 صيغة ملف، كلٌّ منها يُولَّد بحجم دقيق

كلٌّ منها ملف حقيقي من تلك الصيغة. يُفتح في البرنامج الذي يملكه، وعدد بايتاته هو ما طلبته بالضبط. ولا واحد منها أصفار حشوية أُلصق بها امتداد. @@ -259,6 +259,14 @@

27 صيغة ملف، كلٌّ منها يُولَّد بحجم دقيق

full غير منطبق + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -607,6 +615,41 @@

الإعدادات التي تقبلها كل صيغة

bom صحيح أو خاطئ + + mp4 + width + 1 - 16384 بكسل + + + + height + 1 - 16384 بكسل + + + + duration + مدة مثل 10s + + + + change_interval + مدة مثل 10s + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + مدة مثل 10s + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/ar/index.html b/web/public/ar/index.html index cf445a8f..1b176946 100644 --- a/web/public/ar/index.html +++ b/web/public/ar/index.html @@ -4,7 +4,7 @@ -مولّد ملفات الاختبار لضمان الجودة - حجم دقيق، 27 صيغة حقيقية +مولّد ملفات الاختبار لضمان الجودة - حجم دقيق، 28 صيغة حقيقية @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "مولّد ملفات اختبار مجاني ومفتوح المصدر لضمان الجودة. ينتج ملفات حقيقية بـ 27 صيغة وبحجم دقيق، ويكتب بيانًا يحدد كيف يجب أن يتفاعل النظام قيد الاختبار مع كل ملف.", + "description": "مولّد ملفات اختبار مجاني ومفتوح المصدر لضمان الجودة. ينتج ملفات حقيقية بـ 28 صيغة وبحجم دقيق، ويكتب بيانًا يحدد كيف يجب أن يتفاعل النظام قيد الاختبار مع كل ملف.", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

ولّد ملفات اختبار حقيقية بحجم دقيق

- PDF وPNG وDOCX وZIP، 27 صيغة في المجمل، وكلٌّ منها ملف حقيقي + PDF وPNG وDOCX وZIP، 28 صيغة في المجمل، وكلٌّ منها ملف حقيقي يُفتح في البرنامج الذي يملكه، وبـالحجم الذي طلبته تمامًا. كما يدوّن كل تشغيل ما يجب أن يفعله تطبيقك بكل ملف. سطر أوامر ونافذة سطح مكتب، مجاني ومفتوح المصدر، ويعمل بالكامل على جهازك. @@ -179,7 +179,7 @@

ولّد ملفات اختبار حقيقية بحجم دقيق

  • - 27 + 28

    صيغة حقيقية، وكلٌّ منها يُفتح في البرنامج الذي يملكه

  • @@ -340,7 +340,7 @@

    مبني لمجموعة اختبارات تعمل دون إشراف

    اطلب 10485761 بايتًا واحصل على هذا العدد بالضبط. الحجم الذي لا تستطيع الصيغة بلوغه خطأ مع سبب، وليس ملفًا بحجم خاطئ أبدًا.

  • -

    27 صيغة حقيقية

    +

    28 صيغة حقيقية

    ليست أصفارًا حشوية بامتداد. يُفتح ملف PNG المولَّد في عارض الصور، ويُفتح DOCX في Word، ويُفك ضغط ZIP. وتُفحص كل صيغة بقرّاء مستقلين قبل إصدارها.

  • diff --git a/web/public/ar/presets/empty-and-minimal/index.html b/web/public/ar/presets/empty-and-minimal/index.html index e2c71799..4e9cdf7a 100644 --- a/web/public/ar/presets/empty-and-minimal/index.html +++ b/web/public/ar/presets/empty-and-minimal/index.html @@ -5,7 +5,7 @@ أصغر ملفات اختبار صالحة وفارغة في كل صيغة - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -178,10 +178,10 @@

    ماذا في المجموعة؟

    - - - - + + + +
    الملفات29
    الأهداف في وصفته29
    الحجم الإجمالي35 951 B
    الصيغavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
    الملفات30
    الأهداف في وصفته30
    الحجم الإجمالي39 263 B
    الصيغavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
    @@ -192,7 +192,7 @@

    ماذا في المجموعة؟

    المتوقعالمعنىالملفات - acceptينبغي أن يقبل نظامك الملف.27 + acceptينبغي أن يقبل نظامك الملف.28 unspecifiedيعتمد على قواعد نظامك. أنت من يقرر، ثم تتحقق من أن ما يحدث هو ما قصدته.2 diff --git a/web/public/assets/social-preview.png b/web/public/assets/social-preview.png index b777e3f1..5d38d8e0 100644 Binary files a/web/public/assets/social-preview.png and b/web/public/assets/social-preview.png differ diff --git a/web/public/corrupt-test-files/index.html b/web/public/corrupt-test-files/index.html index cd843e65..dc73fed7 100644 --- a/web/public/corrupt-test-files/index.html +++ b/web/public/corrupt-test-files/index.html @@ -201,7 +201,7 @@

    A damaged file is still the size you asked for

    tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

    Settings go after a colon. The flag can be repeated, and the damages are applied in the order you - write them. It works with every one of the 27 formats. + write them. It works with every one of the 28 formats.

    diff --git a/web/public/cs/faq/index.html b/web/public/cs/faq/index.html index e039318f..dd813888 100644 --- a/web/public/cs/faq/index.html +++ b/web/public/cs/faq/index.html @@ -206,7 +206,7 @@

    Často kladené otázky

    Které formáty přijdou dál?

    -

    7z, mp3 a mp4. Dnes funguje od začátku do konce 27 formátů.

    +

    7z, mp3 a mp4. Dnes funguje od začátku do konce 28 formátů.

    @@ -277,7 +277,7 @@

    Často kladené otázky

    { "@type": "Question", "name": "Které formáty přijdou dál?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 a mp4. Dnes funguje od začátku do konce 27 formátů." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 a mp4. Dnes funguje od začátku do konce 28 formátů." } }, { "@type": "Question", diff --git a/web/public/cs/formaty/index.html b/web/public/cs/formaty/index.html index 96fb704d..ba856e2d 100644 --- a/web/public/cs/formaty/index.html +++ b/web/public/cs/formaty/index.html @@ -4,8 +4,8 @@ -27 podporovaných formátů souborů - PDF, DOCX, PNG, ZIP a další - +28 podporovaných formátů souborů - PDF, DOCX, PNG, ZIP a další + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
    -

    27 formátů souborů, každý generovaný v přesné velikosti

    +

    28 formátů souborů, každý generovaný v přesné velikosti

    Každý z nich je skutečný soubor toho formátu. Otevře se v programu, kam patří, a má přesně tolik bajtů, kolik jste požádali. Žádný není vycpávka z nul s přilepenou příponou. @@ -259,6 +259,14 @@

    27 formátů souborů, každý generovaný v přesné velikosti

    full nelze použít + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -608,6 +616,41 @@

    Nastavení, která každý formát přijímá

    bom pravda nebo nepravda + + mp4 + width + 1 - 16384 pixelů + + + + height + 1 - 16384 pixelů + + + + duration + délka, například 10s + + + + change_interval + délka, například 10s + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + délka, například 10s + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/cs/index.html b/web/public/cs/index.html index 9625c627..f75d7a84 100644 --- a/web/public/cs/index.html +++ b/web/public/cs/index.html @@ -4,7 +4,7 @@ -Generátor testovacích souborů - přesná velikost, 27 formátů +Generátor testovacích souborů - přesná velikost, 28 formátů @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "Bezplatný open source generátor testovacích souborů pro QA. Vytváří skutečné soubory v 27 formátech v přesné velikosti a zapisuje manifest, který říká, jak má testovaný systém na každý z nich reagovat.", + "description": "Bezplatný open source generátor testovacích souborů pro QA. Vytváří skutečné soubory v 28 formátech v přesné velikosti a zapisuje manifest, který říká, jak má testovaný systém na každý z nich reagovat.", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

    Generujte skutečné testovací soubory v přesné velikosti

    - PDF, PNG, DOCX, ZIP - celkem 27 formátů a každý je skutečný + PDF, PNG, DOCX, ZIP - celkem 28 formátů a každý je skutečný soubor, který se otevře v programu, kam patří, v přesně té velikosti, o kterou jste požádali. Každý běh také zapisuje, co má vaše aplikace s každým souborem udělat. Příkazový řádek a desktopové okno, zdarma a open source, celé na vašem počítači. @@ -179,7 +179,7 @@

    Generujte skutečné testovací soubory v přesné velikosti

    • - 27 + 28

      skutečných formátů, každý se otevře v programu, kam patří

    • @@ -341,7 +341,7 @@

      Vytvořeno pro sadu testů, která běží bez dozoru

      Požádejte o 10485761 bajtů a dostanete přesně tolik. Velikost, které formát nedosáhne, je chyba s důvodem, nikdy soubor špatné velikosti.

    • -

      27 skutečných formátů

      +

      28 skutečných formátů

      Ne vycpávkové nuly s příponou. Vygenerované PNG se otevře v prohlížeči obrázků, DOCX ve Wordu, ZIP se rozbalí. Každý formát je před vydáním ověřen nezávislými čtečkami.

    • diff --git a/web/public/cs/poskozene-testovaci-soubory/index.html b/web/public/cs/poskozene-testovaci-soubory/index.html index 00d7b92c..69a3724d 100644 --- a/web/public/cs/poskozene-testovaci-soubory/index.html +++ b/web/public/cs/poskozene-testovaci-soubory/index.html @@ -200,7 +200,7 @@

      Poškozený soubor má stále velikost, kterou jste chtěli

      tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

      Nastavení se píše za dvojtečku. Přepínač lze opakovat a poškození se použijí v pořadí, v jakém je - zapíšete. Funguje s každým z 27 formátů. + zapíšete. Funguje s každým z 28 formátů.

      diff --git a/web/public/cs/predvolby/empty-and-minimal/index.html b/web/public/cs/predvolby/empty-and-minimal/index.html index bbe20269..4917cf82 100644 --- a/web/public/cs/predvolby/empty-and-minimal/index.html +++ b/web/public/cs/predvolby/empty-and-minimal/index.html @@ -5,7 +5,7 @@ Nejmenší platné a prázdné testovací soubory v každém formátu - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -178,10 +178,10 @@

      Co je v sadě?

      - - - - + + + +
      Soubory29
      Targety v receptu29
      Celková velikost35 951 B
      Formátyavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
      Soubory30
      Targety v receptu30
      Celková velikost39 263 B
      Formátyavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
      @@ -192,7 +192,7 @@

      Co je v sadě?

      OčekávánoVýznamSoubory - acceptVáš systém má soubor přijmout.27 + acceptVáš systém má soubor přijmout.28 unspecifiedZáleží na pravidlech vašeho systému. Rozhodnete vy, a pak ověříte, že to, co se stane, je to, co jste chtěli.2 diff --git a/web/public/de/beschaedigte-testdateien/index.html b/web/public/de/beschaedigte-testdateien/index.html index 543ab314..c9927666 100644 --- a/web/public/de/beschaedigte-testdateien/index.html +++ b/web/public/de/beschaedigte-testdateien/index.html @@ -203,7 +203,7 @@

      Eine beschädigte Datei hat trotzdem die verlangte Größe

      Einstellungen stehen nach einem Doppelpunkt. Die Option lässt sich wiederholen, und die Beschädigungen werden in der Reihenfolge angewendet, in der Sie sie schreiben. Es funktioniert - mit allen 27 Formaten. + mit allen 28 Formaten.

      diff --git a/web/public/de/faq/index.html b/web/public/de/faq/index.html index 2712d41c..ac583eed 100644 --- a/web/public/de/faq/index.html +++ b/web/public/de/faq/index.html @@ -206,7 +206,7 @@

      Häufig gestellte Fragen

      Welche Formate kommen als Nächstes?

      -

      7z, mp3 und mp4. 27 Formate funktionieren heute durchgängig.

      +

      7z, mp3 und mp4. 28 Formate funktionieren heute durchgängig.

      @@ -277,7 +277,7 @@

      Häufig gestellte Fragen

      { "@type": "Question", "name": "Welche Formate kommen als Nächstes?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 und mp4. 27 Formate funktionieren heute durchgängig." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 und mp4. 28 Formate funktionieren heute durchgängig." } }, { "@type": "Question", diff --git a/web/public/de/formate/index.html b/web/public/de/formate/index.html index bc06e3e5..9d8d2bf9 100644 --- a/web/public/de/formate/index.html +++ b/web/public/de/formate/index.html @@ -4,8 +4,8 @@ -27 unterstützte Dateiformate - PDF, DOCX, PNG, ZIP und mehr - +28 unterstützte Dateiformate - PDF, DOCX, PNG, ZIP und mehr + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
      -

      27 Dateiformate, jedes in exakter Größe erzeugt

      +

      28 Dateiformate, jedes in exakter Größe erzeugt

      Jedes davon ist eine echte Datei dieses Formats. Sie öffnet sich in der Software, zu der sie gehört, und hat genau die Byte-Zahl, die du verlangt hast. Keines davon besteht aus @@ -260,6 +260,14 @@

      27 Dateiformate, jedes in exakter Größe erzeugt

      full nicht zutreffend + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -610,6 +618,41 @@

      Einstellungen, die jedes Format kennt

      bom wahr oder falsch + + mp4 + width + 1 - 16384 Pixel + + + + height + 1 - 16384 Pixel + + + + duration + eine Dauer wie 10s + + + + change_interval + eine Dauer wie 10s + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + eine Dauer wie 10s + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/de/index.html b/web/public/de/index.html index c10a4d72..82e87105 100644 --- a/web/public/de/index.html +++ b/web/public/de/index.html @@ -4,7 +4,7 @@ -Testdatei-Generator für QA - exakte Größe, 27 echte Formate +Testdatei-Generator für QA - exakte Größe, 28 echte Formate @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "Ein kostenloser Open-Source-Generator für Testdateien in der QA. Er erzeugt echte Dateien in 27 Formaten in jeder exakten Größe und schreibt ein Manifest, das beschreibt, wie das getestete System auf jede Datei reagieren soll.", + "description": "Ein kostenloser Open-Source-Generator für Testdateien in der QA. Er erzeugt echte Dateien in 28 Formaten in jeder exakten Größe und schreibt ein Manifest, das beschreibt, wie das getestete System auf jede Datei reagieren soll.", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

      Echte Testdateien in jeder exakten Größe erzeugen

      - PDF, PNG, DOCX, ZIP - insgesamt 27 Formate, und jedes ist + PDF, PNG, DOCX, ZIP - insgesamt 28 Formate, und jedes ist eine echte Datei, die sich in der Software öffnet, zu der sie gehört, in genau der Größe, die du verlangt hast. Jeder Lauf hält außerdem fest, was deine Anwendung mit jeder Datei tun soll. Kommandozeile und Desktop-Fenster, kostenlos und Open Source, vollständig @@ -180,7 +180,7 @@

      Echte Testdateien in jeder exakten Größe erzeugen

      • - 27 + 28

        echte Formate, jedes öffnet sich in der Software, zu der es gehört

      • @@ -345,7 +345,7 @@

        Gebaut für eine Testsuite, die unbeaufsichtigt läuft

        Verlange 10485761 Bytes und bekomme genau das. Eine Größe, die ein Format nicht erreichen kann, ist ein Fehler mit Begründung, nie eine Datei in der falschen Größe.

      • -

        27 echte Formate

        +

        28 echte Formate

        Keine aufgefüllten Nullen mit einer Endung. Ein erzeugtes PNG öffnet sich im Bildbetrachter, ein DOCX in Word, ein ZIP lässt sich entpacken. Jedes wird vor der Auslieferung mit unabhängigen Readern geprüft.

      • diff --git a/web/public/de/presets/empty-and-minimal/index.html b/web/public/de/presets/empty-and-minimal/index.html index 98e1c32d..98e98355 100644 --- a/web/public/de/presets/empty-and-minimal/index.html +++ b/web/public/de/presets/empty-and-minimal/index.html @@ -5,7 +5,7 @@ Kleinste gültige und leere Testdateien in jedem Format - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -178,10 +178,10 @@

        Was steckt im Set?

        - - - - + + + +
        Dateien29
        Targets in seinem Rezept29
        Gesamtgröße35 951 B
        Formateavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
        Dateien30
        Targets in seinem Rezept30
        Gesamtgröße39 263 B
        Formateavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
        @@ -192,7 +192,7 @@

        Was steckt im Set?

        ErwartetBedeutungDateien - acceptDein System soll die Datei annehmen.27 + acceptDein System soll die Datei annehmen.28 unspecifiedDas hängt von den Regeln deines Systems ab. Du entscheidest, dann prüfst du, ob das Ergebnis dem entspricht, was du gemeint hast.2 diff --git a/web/public/es/archivos-de-prueba-corruptos/index.html b/web/public/es/archivos-de-prueba-corruptos/index.html index 2e893ceb..d5de7876 100644 --- a/web/public/es/archivos-de-prueba-corruptos/index.html +++ b/web/public/es/archivos-de-prueba-corruptos/index.html @@ -201,7 +201,7 @@

        Un archivo dañado conserva el tamaño que pediste

        tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

        Los ajustes van después de dos puntos. La opción se puede repetir, y los daños se aplican en el - orden en que los escribes. Funciona con cada uno de los 27 formatos. + orden en que los escribes. Funciona con cada uno de los 28 formatos.

        diff --git a/web/public/es/formatos/index.html b/web/public/es/formatos/index.html index 869494ee..bc8b2bf3 100644 --- a/web/public/es/formatos/index.html +++ b/web/public/es/formatos/index.html @@ -4,8 +4,8 @@ -27 formatos de archivo - PDF, DOCX, PNG, ZIP y más - +28 formatos de archivo - PDF, DOCX, PNG, ZIP y más + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
        -

        27 formatos de archivo, cada uno generado a un tamaño exacto

        +

        28 formatos de archivo, cada uno generado a un tamaño exacto

        Cada uno es un archivo real de ese formato. Se abre en el programa que le corresponde y tiene exactamente el número de bytes que pediste. Ninguno es relleno de ceros con @@ -260,6 +260,14 @@

        27 formatos de archivo, cada uno generado a un tamaño exacto

        full no aplicable + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -611,6 +619,41 @@

        Ajustes que admite cada formato

        bom verdadero o falso + + mp4 + width + 1 - 16384 píxeles + + + + height + 1 - 16384 píxeles + + + + duration + una duración como 10s + + + + change_interval + una duración como 10s + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + una duración como 10s + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/es/index.html b/web/public/es/index.html index a35c8dde..7bae103d 100644 --- a/web/public/es/index.html +++ b/web/public/es/index.html @@ -4,7 +4,7 @@ -Generador de archivos de prueba - tamaño exacto, 27 formatos +Generador de archivos de prueba - tamaño exacto, 28 formatos @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "Un generador gratuito y de código abierto de archivos de prueba para QA. Produce archivos reales de 27 formatos al tamaño exacto y escribe un manifiesto que dice cómo debe reaccionar el sistema bajo prueba a cada uno.", + "description": "Un generador gratuito y de código abierto de archivos de prueba para QA. Produce archivos reales de 28 formatos al tamaño exacto y escribe un manifiesto que dice cómo debe reaccionar el sistema bajo prueba a cada uno.", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

        Genera archivos de prueba reales al tamaño exacto

        - PDF, PNG, DOCX, ZIP - 27 formatos en total, y cada uno es un + PDF, PNG, DOCX, ZIP - 28 formatos en total, y cada uno es un archivo real que se abre en el programa que le corresponde, con exactamente el tamaño que pediste. Cada ejecución también anota qué debe hacer tu aplicación con cada archivo. Línea de comandos y ventana de escritorio, gratis y de código abierto, funcionando por @@ -180,7 +180,7 @@

        Genera archivos de prueba reales al tamaño exacto

        • - 27 + 28

          formatos reales, cada uno se abre en el programa que le corresponde

        • @@ -345,7 +345,7 @@

          Pensado para un conjunto de pruebas que corre sin supervisión

          Pide 10485761 bytes y obtén exactamente eso. Un tamaño que un formato no puede alcanzar es un error con un motivo, nunca un archivo del tamaño equivocado.

        • -

          27 formatos reales

          +

          28 formatos reales

          No ceros de relleno con una extensión. Un PNG generado se abre en un visor de imágenes, un DOCX se abre en Word, un ZIP se extrae. Cada uno se comprueba con lectores independientes antes de publicarse.

        • diff --git a/web/public/es/preguntas-frecuentes/index.html b/web/public/es/preguntas-frecuentes/index.html index e52f2a7a..452b090b 100644 --- a/web/public/es/preguntas-frecuentes/index.html +++ b/web/public/es/preguntas-frecuentes/index.html @@ -206,7 +206,7 @@

          Preguntas frecuentes

          ¿Qué formatos vienen después?

          -

          7z, mp3 y mp4. Hoy funcionan de principio a fin 27 formatos.

          +

          7z, mp3 y mp4. Hoy funcionan de principio a fin 28 formatos.

          @@ -277,7 +277,7 @@

          Preguntas frecuentes

          { "@type": "Question", "name": "¿Qué formatos vienen después?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 y mp4. Hoy funcionan de principio a fin 27 formatos." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 y mp4. Hoy funcionan de principio a fin 28 formatos." } }, { "@type": "Question", diff --git a/web/public/es/presets/empty-and-minimal/index.html b/web/public/es/presets/empty-and-minimal/index.html index 8b3e0601..971e3221 100644 --- a/web/public/es/presets/empty-and-minimal/index.html +++ b/web/public/es/presets/empty-and-minimal/index.html @@ -5,7 +5,7 @@ Archivos de prueba válidos mínimos y vacíos en cada formato - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -179,10 +179,10 @@

          ¿Qué hay en el conjunto?

          - - - - + + + +
          Archivos29
          Targets en su receta29
          Tamaño total35 951 B
          Formatosavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
          Archivos30
          Targets en su receta30
          Tamaño total39 263 B
          Formatosavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
          @@ -193,7 +193,7 @@

          ¿Qué hay en el conjunto?

          EsperadoSignificadoArchivos - acceptTu sistema debe aceptar el archivo.27 + acceptTu sistema debe aceptar el archivo.28 unspecifiedDepende de las reglas de tu sistema. Tú decides y después compruebas que lo que ocurre es lo que querías.2 diff --git a/web/public/faq/index.html b/web/public/faq/index.html index 50fa3abe..9a7b64a6 100644 --- a/web/public/faq/index.html +++ b/web/public/faq/index.html @@ -205,7 +205,7 @@

          Frequently asked questions

          Which formats are coming next?

          -

          7z, mp3 and mp4. 27 formats work end to end today.

          +

          7z, mp3 and mp4. 28 formats work end to end today.

          @@ -276,7 +276,7 @@

          Frequently asked questions

          { "@type": "Question", "name": "Which formats are coming next?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 and mp4. 27 formats work end to end today." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 and mp4. 28 formats work end to end today." } }, { "@type": "Question", diff --git a/web/public/formats/index.html b/web/public/formats/index.html index f9a74933..8b0ae47d 100644 --- a/web/public/formats/index.html +++ b/web/public/formats/index.html @@ -4,8 +4,8 @@ -27 Supported File Formats - PDF, DOCX, PNG, ZIP and More - +28 Supported File Formats - PDF, DOCX, PNG, ZIP and More + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
          -

          27 file formats, every one generated at an exact size

          +

          28 file formats, every one generated at an exact size

          Each of these is a real file of that format. It opens in the software that owns it, and it is exactly the number of bytes you asked for. None of them is padded zeros with an extension @@ -260,6 +260,14 @@

          27 file formats, every one generated at an exact size

          full not applicable + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -609,6 +617,41 @@

          Settings each format accepts

          bom true or false + + mp4 + width + 1 - 16384 pixels + + + + height + 1 - 16384 pixels + + + + duration + a length of time such as 10s + + + + change_interval + a length of time such as 10s + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + a length of time such as 10s + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/fr/faq/index.html b/web/public/fr/faq/index.html index 0d89565b..15a20043 100644 --- a/web/public/fr/faq/index.html +++ b/web/public/fr/faq/index.html @@ -206,7 +206,7 @@

          Questions fréquentes

          Quels formats arrivent ensuite ?

          -

          7z, mp3 et mp4. 27 formats fonctionnent de bout en bout aujourd'hui.

          +

          7z, mp3 et mp4. 28 formats fonctionnent de bout en bout aujourd'hui.

          @@ -277,7 +277,7 @@

          Questions fréquentes

          { "@type": "Question", "name": "Quels formats arrivent ensuite ?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 et mp4. 27 formats fonctionnent de bout en bout aujourd\u0027hui." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 et mp4. 28 formats fonctionnent de bout en bout aujourd\u0027hui." } }, { "@type": "Question", diff --git a/web/public/fr/fichiers-de-test-corrompus/index.html b/web/public/fr/fichiers-de-test-corrompus/index.html index ce61fa3c..59fa9387 100644 --- a/web/public/fr/fichiers-de-test-corrompus/index.html +++ b/web/public/fr/fichiers-de-test-corrompus/index.html @@ -202,7 +202,7 @@

          Un fichier abîmé garde la taille demandée

          tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

          Les réglages se placent après deux-points. L'option peut être répétée, et les dommages s'appliquent - dans l'ordre où vous les écrivez. Ça marche avec chacun des 27 formats. + dans l'ordre où vous les écrivez. Ça marche avec chacun des 28 formats.

          diff --git a/web/public/fr/formats/index.html b/web/public/fr/formats/index.html index 8b31bc21..2de9c31f 100644 --- a/web/public/fr/formats/index.html +++ b/web/public/fr/formats/index.html @@ -4,8 +4,8 @@ -27 formats de fichiers - PDF, DOCX, PNG, ZIP et plus - +28 formats de fichiers - PDF, DOCX, PNG, ZIP et plus + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
          -

          27 formats de fichiers, chacun généré à une taille exacte

          +

          28 formats de fichiers, chacun généré à une taille exacte

          Chacun est un vrai fichier de ce format. Il s'ouvre dans le logiciel qui lui correspond et fait exactement le nombre d'octets que vous avez demandé. Aucun n'est du remplissage @@ -260,6 +260,14 @@

          27 formats de fichiers, chacun généré à une taille exacte

          full sans objet + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -611,6 +619,41 @@

          Réglages que chaque format accepte

          bom vrai ou faux + + mp4 + width + 1 - 16384 pixels + + + + height + 1 - 16384 pixels + + + + duration + une durée comme 10s + + + + change_interval + une durée comme 10s + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + une durée comme 10s + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/fr/index.html b/web/public/fr/index.html index 03a4abef..71dd8be7 100644 --- a/web/public/fr/index.html +++ b/web/public/fr/index.html @@ -4,7 +4,7 @@ -Générateur de fichiers de test - taille exacte, 27 vrais formats +Générateur de fichiers de test - taille exacte, 28 vrais formats @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "Un générateur gratuit et open source de fichiers de test pour la QA. Il produit de vrais fichiers dans 27 formats, à la taille exacte, et écrit un manifeste qui dit comment le système testé doit réagir à chacun.", + "description": "Un générateur gratuit et open source de fichiers de test pour la QA. Il produit de vrais fichiers dans 28 formats, à la taille exacte, et écrit un manifeste qui dit comment le système testé doit réagir à chacun.", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

          Générez de vrais fichiers de test à la taille exacte

          - PDF, PNG, DOCX, ZIP - 27 formats au total, et chacun est un + PDF, PNG, DOCX, ZIP - 28 formats au total, et chacun est un vrai fichier qui s'ouvre dans le logiciel qui lui correspond, à la taille exacte que vous avez demandée. Chaque exécution note aussi ce que votre application doit faire de chaque fichier. Ligne de commande et fenêtre de bureau, gratuit et open source, entièrement sur @@ -180,7 +180,7 @@

          Générez de vrais fichiers de test à la taille exacte

          • - 27 + 28

            vrais formats, chacun s'ouvrant dans le logiciel qui lui correspond

          • @@ -345,7 +345,7 @@

            Conçu pour une suite qui tourne sans surveillance

            Demandez 10485761 octets et obtenez exactement cela. Une taille qu'un format ne peut pas atteindre est une erreur avec une raison, jamais un fichier de mauvaise taille.

          • -

            27 vrais formats

            +

            28 vrais formats

            Pas des zéros de remplissage avec une extension. Un PNG généré s'ouvre dans une visionneuse d'images, un DOCX s'ouvre dans Word, un ZIP se décompresse. Chacun est vérifié avec des lecteurs indépendants avant d'être livré.

          • diff --git a/web/public/fr/prereglages/empty-and-minimal/index.html b/web/public/fr/prereglages/empty-and-minimal/index.html index 2fec158d..37b20d9f 100644 --- a/web/public/fr/prereglages/empty-and-minimal/index.html +++ b/web/public/fr/prereglages/empty-and-minimal/index.html @@ -5,7 +5,7 @@ Fichiers de test valides minimaux et vides, dans chaque format - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -179,10 +179,10 @@

            Que contient le jeu ?

            - - - - + + + +
            Fichiers29
            Cibles dans sa recette29
            Taille totale35 951 B
            Formatsavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
            Fichiers30
            Cibles dans sa recette30
            Taille totale39 263 B
            Formatsavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
            @@ -193,7 +193,7 @@

            Que contient le jeu ?

            AttenduSignificationFichiers - acceptVotre système doit accepter le fichier.27 + acceptVotre système doit accepter le fichier.28 unspecifiedCela dépend des règles de votre système. Vous décidez, puis vous vérifiez que ce qui se passe est ce que vous aviez voulu.2 diff --git a/web/public/hi/corrupt-test-files/index.html b/web/public/hi/corrupt-test-files/index.html index dd91c15c..36f76e09 100644 --- a/web/public/hi/corrupt-test-files/index.html +++ b/web/public/hi/corrupt-test-files/index.html @@ -200,7 +200,7 @@

            खराब फ़ाइल का आकार वही रहता ह tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

            सेटिंग कोलन के बाद लिखी जाती है। विकल्प दोहराया जा सकता है, और बिगाड़ आपके लिखे क्रम में लागू होते - हैं। यह सभी 27 फ़ॉर्मैट के साथ चलता है। + हैं। यह सभी 28 फ़ॉर्मैट के साथ चलता है।

            diff --git a/web/public/hi/faq/index.html b/web/public/hi/faq/index.html index 9427ee63..88b6ca8d 100644 --- a/web/public/hi/faq/index.html +++ b/web/public/hi/faq/index.html @@ -205,7 +205,7 @@

            अक्सर पूछे जाने वाले सवाल

            आगे कौन से फ़ॉर्मैट आ रहे हैं?

            -

            7z, mp3 और mp4। आज 27 फ़ॉर्मैट शुरू से अंत तक काम करते हैं।

            +

            7z, mp3 और mp4। आज 28 फ़ॉर्मैट शुरू से अंत तक काम करते हैं।

            @@ -276,7 +276,7 @@

            अक्सर पूछे जाने वाले सवाल

            { "@type": "Question", "name": "आगे कौन से फ़ॉर्मैट आ रहे हैं?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 और mp4। आज 27 फ़ॉर्मैट शुरू से अंत तक काम करते हैं।" } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 और mp4। आज 28 फ़ॉर्मैट शुरू से अंत तक काम करते हैं।" } }, { "@type": "Question", diff --git a/web/public/hi/formats/index.html b/web/public/hi/formats/index.html index d58ec728..154167bd 100644 --- a/web/public/hi/formats/index.html +++ b/web/public/hi/formats/index.html @@ -4,8 +4,8 @@ -27 समर्थित फ़ाइल फ़ॉर्मैट - PDF, DOCX, PNG, ZIP और अन्य - +28 समर्थित फ़ाइल फ़ॉर्मैट - PDF, DOCX, PNG, ZIP और अन्य + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
            -

            27 फ़ाइल फ़ॉर्मैट, हर एक सटीक आकार में बनाया गया

            +

            28 फ़ाइल फ़ॉर्मैट, हर एक सटीक आकार में बनाया गया

            इनमें से हर एक उस फ़ॉर्मैट की असली फ़ाइल है। वह अपने सॉफ़्टवेयर में खुलती है और उसमें ठीक उतने बाइट हैं जितने आपने माँगे। इनमें से कोई भी एक्सटेंशन चिपकाए गए भराव के शून्य नहीं @@ -260,6 +260,14 @@

            27 फ़ाइल फ़ॉर्मैट, हर एक सटीक आ full लागू नहीं + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -609,6 +617,41 @@

            हर फ़ॉर्मैट की स्वीकार की जा bom सही या गलत + + mp4 + width + 1 - 16384 पिक्सेल + + + + height + 1 - 16384 पिक्सेल + + + + duration + 10s जैसी अवधि + + + + change_interval + 10s जैसी अवधि + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + 10s जैसी अवधि + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/hi/index.html b/web/public/hi/index.html index 2a061380..24a37f25 100644 --- a/web/public/hi/index.html +++ b/web/public/hi/index.html @@ -4,7 +4,7 @@ -QA के लिए टेस्ट फ़ाइल जनरेटर - सटीक आकार, 27 असली फ़ॉर्मैट +QA के लिए टेस्ट फ़ाइल जनरेटर - सटीक आकार, 28 असली फ़ॉर्मैट @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "QA के लिए मुफ़्त और ओपन सोर्स टेस्ट फ़ाइल जनरेटर। यह 27 फ़ॉर्मैट में सटीक आकार की असली फ़ाइलें बनाता है और एक मैनिफ़ेस्ट लिखता है जो बताता है कि जाँचे जा रहे सिस्टम को हर फ़ाइल पर क्या प्रतिक्रिया देनी चाहिए।", + "description": "QA के लिए मुफ़्त और ओपन सोर्स टेस्ट फ़ाइल जनरेटर। यह 28 फ़ॉर्मैट में सटीक आकार की असली फ़ाइलें बनाता है और एक मैनिफ़ेस्ट लिखता है जो बताता है कि जाँचे जा रहे सिस्टम को हर फ़ाइल पर क्या प्रतिक्रिया देनी चाहिए।", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

            सटीक आकार की असली टेस्ट फ़ाइलें बनाएँ

            - PDF, PNG, DOCX, ZIP - कुल 27 फ़ॉर्मैट, और हर एक असली फ़ाइल है + PDF, PNG, DOCX, ZIP - कुल 28 फ़ॉर्मैट, और हर एक असली फ़ाइल है जो अपने सॉफ़्टवेयर में खुलती है, ठीक उसी आकार में जो आपने माँगा। हर रन यह भी लिखता है कि आपके एप्लिकेशन को हर फ़ाइल के साथ क्या करना चाहिए। कमांड लाइन और डेस्कटॉप विंडो, मुफ़्त और ओपन सोर्स, पूरी तरह आपकी मशीन पर चलता है। @@ -179,7 +179,7 @@

            सटीक आकार की असली टेस्ट फ़ाइ
            • - 27 + 28

              असली फ़ॉर्मैट, हर एक अपने सॉफ़्टवेयर में खुलता है

            • @@ -341,7 +341,7 @@

              बिना निगरानी के चलने वाले सू

              10485761 बाइट माँगें और ठीक वही पाएँ। जिस आकार तक कोई फ़ॉर्मैट नहीं पहुँच सकता, वह कारण सहित त्रुटि है, गलत आकार की फ़ाइल कभी नहीं।

            • -

              27 असली फ़ॉर्मैट

              +

              28 असली फ़ॉर्मैट

              एक्सटेंशन वाले भराव के शून्य नहीं। बनाई गई PNG इमेज व्यूअर में खुलती है, DOCX Word में खुलता है, ZIP खुल जाता है। हर एक को भेजने से पहले स्वतंत्र रीडरों से जाँचा जाता है।

            • diff --git a/web/public/hi/presets/empty-and-minimal/index.html b/web/public/hi/presets/empty-and-minimal/index.html index f9f8d505..b4c85f99 100644 --- a/web/public/hi/presets/empty-and-minimal/index.html +++ b/web/public/hi/presets/empty-and-minimal/index.html @@ -5,7 +5,7 @@ हर फ़ॉर्मैट की सबसे छोटी वैध और खाली टेस्ट फ़ाइलें - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -178,10 +178,10 @@

              सेट में क्या है?

              - - - - + + + +
              फ़ाइलें29
              इसकी रेसिपी में टार्गेट29
              कुल आकार35 951 B
              फ़ॉर्मैटavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
              फ़ाइलें30
              इसकी रेसिपी में टार्गेट30
              कुल आकार39 263 B
              फ़ॉर्मैटavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
              @@ -192,7 +192,7 @@

              सेट में क्या है?

              अपेक्षितअर्थफ़ाइलें - acceptआपके सिस्टम को फ़ाइल मंज़ूर करनी चाहिए।27 + acceptआपके सिस्टम को फ़ाइल मंज़ूर करनी चाहिए।28 unspecifiedयह आपके सिस्टम के नियमों पर निर्भर है। आप तय करें, फिर जाँचें कि जो होता है वही है जो आप चाहते थे।2 diff --git a/web/public/id/faq/index.html b/web/public/id/faq/index.html index 7a9903f6..f0575576 100644 --- a/web/public/id/faq/index.html +++ b/web/public/id/faq/index.html @@ -206,7 +206,7 @@

              Pertanyaan yang sering diajukan

              Format apa yang akan hadir berikutnya?

              -

              7z, mp3, dan mp4. Saat ini 27 format bekerja dari ujung ke ujung.

              +

              7z, mp3, dan mp4. Saat ini 28 format bekerja dari ujung ke ujung.

              @@ -277,7 +277,7 @@

              Pertanyaan yang sering diajukan

              { "@type": "Question", "name": "Format apa yang akan hadir berikutnya?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3, dan mp4. Saat ini 27 format bekerja dari ujung ke ujung." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3, dan mp4. Saat ini 28 format bekerja dari ujung ke ujung." } }, { "@type": "Question", diff --git a/web/public/id/file-uji-rusak/index.html b/web/public/id/file-uji-rusak/index.html index ffc047b9..41a8702a 100644 --- a/web/public/id/file-uji-rusak/index.html +++ b/web/public/id/file-uji-rusak/index.html @@ -201,7 +201,7 @@

              File yang rusak tetap berukuran seperti yang Anda minta

              tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

              Pengaturan ditulis setelah titik dua. Opsi ini bisa diulang, dan kerusakan diterapkan menurut urutan - yang Anda tulis. Berlaku untuk semua 27 format. + yang Anda tulis. Berlaku untuk semua 28 format.

              diff --git a/web/public/id/format/index.html b/web/public/id/format/index.html index 918f7bf4..796c1951 100644 --- a/web/public/id/format/index.html +++ b/web/public/id/format/index.html @@ -4,8 +4,8 @@ -27 Format File yang Didukung - PDF, DOCX, PNG, ZIP, dan Lainnya - +28 Format File yang Didukung - PDF, DOCX, PNG, ZIP, dan Lainnya + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
              -

              27 format file, masing-masing dibuat dengan ukuran tepat

              +

              28 format file, masing-masing dibuat dengan ukuran tepat

              Masing-masing adalah file asli dari format itu. File terbuka di aplikasi yang memilikinya dan persis berjumlah byte yang Anda minta. Tidak ada yang berupa nol pengisi dengan @@ -260,6 +260,14 @@

              27 format file, masing-masing dibuat dengan ukuran tepat

              full tidak berlaku + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -609,6 +617,41 @@

              Pengaturan yang diterima setiap format

              bom benar atau salah + + mp4 + width + 1 - 16384 piksel + + + + height + 1 - 16384 piksel + + + + duration + durasi seperti 10s + + + + change_interval + durasi seperti 10s + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + durasi seperti 10s + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/id/index.html b/web/public/id/index.html index 55dfd198..630738ee 100644 --- a/web/public/id/index.html +++ b/web/public/id/index.html @@ -4,7 +4,7 @@ -Generator File Uji untuk QA - Ukuran Tepat, 27 Format Asli +Generator File Uji untuk QA - Ukuran Tepat, 28 Format Asli @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "Generator file uji untuk QA yang gratis dan open source. Alat ini membuat file asli dalam 27 format dengan ukuran tepat dan menulis manifes yang menyatakan bagaimana sistem yang diuji harus bereaksi terhadap tiap file.", + "description": "Generator file uji untuk QA yang gratis dan open source. Alat ini membuat file asli dalam 28 format dengan ukuran tepat dan menulis manifes yang menyatakan bagaimana sistem yang diuji harus bereaksi terhadap tiap file.", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

              Buat file uji asli dengan ukuran tepat

              - PDF, PNG, DOCX, ZIP - 27 format seluruhnya, dan setiap file + PDF, PNG, DOCX, ZIP - 28 format seluruhnya, dan setiap file adalah file asli yang terbuka di aplikasi yang memilikinya, dengan ukuran persis seperti yang Anda minta. Setiap run juga mencatat apa yang harus dilakukan aplikasi Anda terhadap tiap file. Baris perintah dan jendela desktop, gratis dan open source, bekerja @@ -180,7 +180,7 @@

              Buat file uji asli dengan ukuran tepat

              • - 27 + 28

                format asli, masing-masing terbuka di aplikasi yang memilikinya

              • @@ -344,7 +344,7 @@

                Dibuat untuk suite yang berjalan tanpa pengawasan

                Minta 10485761 byte dan dapatkan persis itu. Ukuran yang tidak dapat dicapai format adalah galat dengan alasan, tidak pernah file berukuran salah.

              • -

                27 format asli

                +

                28 format asli

                Bukan nol pengisi dengan ekstensi. PNG yang dihasilkan terbuka di penampil gambar, DOCX terbuka di Word, ZIP bisa diekstrak. Masing-masing diperiksa dengan pembaca independen sebelum dirilis.

              • diff --git a/web/public/id/preset/empty-and-minimal/index.html b/web/public/id/preset/empty-and-minimal/index.html index 6e31111a..02fae09f 100644 --- a/web/public/id/preset/empty-and-minimal/index.html +++ b/web/public/id/preset/empty-and-minimal/index.html @@ -5,7 +5,7 @@ File Uji Valid Terkecil dan Kosong di Setiap Format - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -179,10 +179,10 @@

                Apa isi set?

                - - - - + + + +
                File29
                Target dalam resepnya29
                Ukuran total35 951 B
                Formatavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                File30
                Target dalam resepnya30
                Ukuran total39 263 B
                Formatavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                @@ -193,7 +193,7 @@

                Apa isi set?

                DiharapkanArtiFile - acceptSistem Anda harus menerima file ini.27 + acceptSistem Anda harus menerima file ini.28 unspecifiedTergantung pada aturan sistem Anda. Anda yang memutuskan, lalu memeriksa apakah yang terjadi sesuai maksud Anda.2 diff --git a/web/public/index.html b/web/public/index.html index 30f1719a..a1d690af 100644 --- a/web/public/index.html +++ b/web/public/index.html @@ -4,7 +4,7 @@ -Test File Generator for QA - Exact Size, 27 Real Formats +Test File Generator for QA - Exact Size, 28 Real Formats @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "A free and open source generator of test files for QA. It produces real files of 27 formats at any exact size and writes a manifest saying how the system under test should react to each one.", + "description": "A free and open source generator of test files for QA. It produces real files of 28 formats at any exact size and writes a manifest saying how the system under test should react to each one.", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

                Generate real test files at any exact size

                - PDF, PNG, DOCX, ZIP - 27 formats in all, and every one is a + PDF, PNG, DOCX, ZIP - 28 formats in all, and every one is a real file that opens in the software that owns it, at exactly the size you asked for. Each run also writes down what your application is supposed to do with each file. Command line and desktop window, free and open source, working entirely on your machine. @@ -179,7 +179,7 @@

                Generate real test files at any exact size

                • - 27 + 28

                  real formats, each one opening in the software that owns it

                • @@ -342,7 +342,7 @@

                  Built for a suite that runs unattended

                  Ask for 10485761 bytes and get exactly that. A size a format cannot reach is an error with a reason, never a file of the wrong size.

                • -

                  27 real formats

                  +

                  28 real formats

                  Not padded zeros with an extension. A generated PNG opens in an image viewer, a DOCX opens in Word, a ZIP extracts. Each one is checked against independent readers before it ships.

                • diff --git a/web/public/it/domande-frequenti/index.html b/web/public/it/domande-frequenti/index.html index de61916b..a39c55e2 100644 --- a/web/public/it/domande-frequenti/index.html +++ b/web/public/it/domande-frequenti/index.html @@ -206,7 +206,7 @@

                  Domande frequenti

                  Quali formati arrivano dopo?

                  -

                  7z, mp3 e mp4. Oggi 27 formati funzionano da un capo all'altro.

                  +

                  7z, mp3 e mp4. Oggi 28 formati funzionano da un capo all'altro.

                  @@ -277,7 +277,7 @@

                  Domande frequenti

                  { "@type": "Question", "name": "Quali formati arrivano dopo?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 e mp4. Oggi 27 formati funzionano da un capo all\u0027altro." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 e mp4. Oggi 28 formati funzionano da un capo all\u0027altro." } }, { "@type": "Question", diff --git a/web/public/it/file-di-test-corrotti/index.html b/web/public/it/file-di-test-corrotti/index.html index 334c9522..73f35a32 100644 --- a/web/public/it/file-di-test-corrotti/index.html +++ b/web/public/it/file-di-test-corrotti/index.html @@ -201,7 +201,7 @@

                  Un file danneggiato ha ancora la dimensione richiesta

                  tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

                  Le impostazioni vanno dopo i due punti. L'opzione si può ripetere, e i danni vengono applicati - nell'ordine in cui li scrivi. Funziona con ognuno dei 27 formati. + nell'ordine in cui li scrivi. Funziona con ognuno dei 28 formati.

                  diff --git a/web/public/it/formati/index.html b/web/public/it/formati/index.html index 32c6ad41..b183deaa 100644 --- a/web/public/it/formati/index.html +++ b/web/public/it/formati/index.html @@ -4,8 +4,8 @@ -27 formati di file supportati - PDF, DOCX, PNG, ZIP e altri - +28 formati di file supportati - PDF, DOCX, PNG, ZIP e altri + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
                  -

                  27 formati di file, ognuno generato a una dimensione esatta

                  +

                  28 formati di file, ognuno generato a una dimensione esatta

                  Ognuno è un file reale di quel formato. Si apre nel programma che gli appartiene ed è esattamente il numero di byte che hai chiesto. Nessuno è riempimento di zeri con un'estensione @@ -260,6 +260,14 @@

                  27 formati di file, ognuno generato a una dimensione esatta

                  full non applicabile + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -611,6 +619,41 @@

                  Impostazioni che ogni formato accetta

                  bom vero o falso + + mp4 + width + 1 - 16384 pixel + + + + height + 1 - 16384 pixel + + + + duration + una durata come 10s + + + + change_interval + una durata come 10s + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + una durata come 10s + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/it/index.html b/web/public/it/index.html index e98133b1..29124e9b 100644 --- a/web/public/it/index.html +++ b/web/public/it/index.html @@ -4,7 +4,7 @@ -Generatore di file di test - dimensione esatta, 27 formati reali +Generatore di file di test - dimensione esatta, 28 formati reali @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "Un generatore gratuito e open source di file di test per la QA. Produce file reali in 27 formati della dimensione esatta e scrive un manifest che dice come il sistema sotto test deve reagire a ciascuno.", + "description": "Un generatore gratuito e open source di file di test per la QA. Produce file reali in 28 formati della dimensione esatta e scrive un manifest che dice come il sistema sotto test deve reagire a ciascuno.", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

                  Genera file di test reali della dimensione esatta

                  - PDF, PNG, DOCX, ZIP - 27 formati in tutto, e ognuno è un file + PDF, PNG, DOCX, ZIP - 28 formati in tutto, e ognuno è un file reale che si apre nel programma che gli appartiene, di esattamente la dimensione che hai chiesto. Ogni esecuzione annota anche cosa la tua applicazione deve fare con ogni file. Riga di comando e finestra desktop, gratuito e open source, interamente sulla tua macchina. @@ -179,7 +179,7 @@

                  Genera file di test reali della dimensione esatta

                  • - 27 + 28

                    formati reali, ognuno si apre nel programma che gli appartiene

                  • @@ -344,7 +344,7 @@

                    Costruito per una suite che gira senza supervisione

                    Chiedi 10485761 byte e ottieni esattamente quelli. Una dimensione che un formato non può raggiungere è un errore con un motivo, mai un file della dimensione sbagliata.

                  • -

                    27 formati reali

                    +

                    28 formati reali

                    Non zeri di riempimento con un'estensione. Un PNG generato si apre in un visualizzatore di immagini, un DOCX si apre in Word, uno ZIP si estrae. Ognuno è verificato con lettori indipendenti prima del rilascio.

                  • diff --git a/web/public/it/preset/empty-and-minimal/index.html b/web/public/it/preset/empty-and-minimal/index.html index 81e46c2b..40d49f4e 100644 --- a/web/public/it/preset/empty-and-minimal/index.html +++ b/web/public/it/preset/empty-and-minimal/index.html @@ -5,7 +5,7 @@ File di test validi minimi e vuoti in ogni formato - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -179,10 +179,10 @@

                    Cosa c'è nel set?

                    - - - - + + + +
                    File29
                    Target nella sua ricetta29
                    Dimensione totale35 951 B
                    Formatiavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                    File30
                    Target nella sua ricetta30
                    Dimensione totale39 263 B
                    Formatiavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                    @@ -193,7 +193,7 @@

                    Cosa c'è nel set?

                    AttesoSignificatoFile - acceptIl tuo sistema deve accettare il file.27 + acceptIl tuo sistema deve accettare il file.28 unspecifiedDipende dalle regole del tuo sistema. Decidi tu, poi controlli che ciò che accade sia ciò che intendevi.2 diff --git a/web/public/ja/corrupt-test-files/index.html b/web/public/ja/corrupt-test-files/index.html index 2af95041..147901ac 100644 --- a/web/public/ja/corrupt-test-files/index.html +++ b/web/public/ja/corrupt-test-files/index.html @@ -189,7 +189,7 @@

                    破損したファイルも、求めたサイズのままです

                    tfg generate --format png --size 2mb --damage zero-head --out ./out
                     tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

                    - 設定はコロンの後ろに書きます。オプションは繰り返せて、破損は書いた順に適用されます。27種類すべての形式で使えます。 + 設定はコロンの後ろに書きます。オプションは繰り返せて、破損は書いた順に適用されます。28種類すべての形式で使えます。

                    diff --git a/web/public/ja/faq/index.html b/web/public/ja/faq/index.html index cd83d7e9..021066ad 100644 --- a/web/public/ja/faq/index.html +++ b/web/public/ja/faq/index.html @@ -204,7 +204,7 @@

                    よくある質問

                    次に対応する形式は何ですか。

                    -

                    7z、mp3、mp4です。現在、27種類の形式がエンドツーエンドで動作します。

                    +

                    7z、mp3、mp4です。現在、28種類の形式がエンドツーエンドで動作します。

                    @@ -275,7 +275,7 @@

                    よくある質問

                    { "@type": "Question", "name": "次に対応する形式は何ですか。", - "acceptedAnswer": { "@type": "Answer", "text": "7z、mp3、mp4です。現在、27種類の形式がエンドツーエンドで動作します。" } + "acceptedAnswer": { "@type": "Answer", "text": "7z、mp3、mp4です。現在、28種類の形式がエンドツーエンドで動作します。" } }, { "@type": "Question", diff --git a/web/public/ja/formats/index.html b/web/public/ja/formats/index.html index 348698c9..dc7aa39e 100644 --- a/web/public/ja/formats/index.html +++ b/web/public/ja/formats/index.html @@ -4,8 +4,8 @@ -対応する27種類のファイル形式 - PDF、DOCX、PNG、ZIPほか - +対応する28種類のファイル形式 - PDF、DOCX、PNG、ZIPほか + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
                    -

                    27種類のファイル形式、どれも正確なサイズで生成

                    +

                    28種類のファイル形式、どれも正確なサイズで生成

                    どれもその形式の本物のファイルです。対応するソフトウェアで開け、要求したバイト数とぴったり一致します。拡張子を貼り付けただけのゼロ埋めはひとつもありません。

                    @@ -258,6 +258,14 @@

                    27種類のファイル形式、どれも正確なサイズで生成

                    full 該当なし + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -601,6 +609,41 @@

                    形式ごとに受け付ける設定

                    bom 真または偽 + + mp4 + width + 1 - 16384 ピクセル + + + + height + 1 - 16384 ピクセル + + + + duration + 10sのような長さ + + + + change_interval + 10sのような長さ + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + 10sのような長さ + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/ja/index.html b/web/public/ja/index.html index f7d37afc..1fdd6248 100644 --- a/web/public/ja/index.html +++ b/web/public/ja/index.html @@ -4,7 +4,7 @@ -テストファイル生成ツール - 正確なサイズ、27種類の本物の形式 +テストファイル生成ツール - 正確なサイズ、28種類の本物の形式 @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "QA向けの無料オープンソースのテストファイル生成ツールです。27種類の形式で正確なサイズの本物のファイルを作り、テスト対象のシステムが各ファイルにどう反応すべきかを示すマニフェストを書き出します。", + "description": "QA向けの無料オープンソースのテストファイル生成ツールです。28種類の形式で正確なサイズの本物のファイルを作り、テスト対象のシステムが各ファイルにどう反応すべきかを示すマニフェストを書き出します。", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

                    正確なサイズの本物のテストファイルを生成

                    - PDF、PNG、DOCX、ZIPなど、全27種類の形式に対応し、どれも対応するソフトウェアで開ける本物のファイルで、サイズは要求したとおりちょうどです。各実行では、アプリが各ファイルをどう扱うべきかも書き出します。コマンドラインとデスクトップウィンドウの両方に対応し、無料のオープンソースで、すべてあなたのマシン上で動きます。 + PDF、PNG、DOCX、ZIPなど、全28種類の形式に対応し、どれも対応するソフトウェアで開ける本物のファイルで、サイズは要求したとおりちょうどです。各実行では、アプリが各ファイルをどう扱うべきかも書き出します。コマンドラインとデスクトップウィンドウの両方に対応し、無料のオープンソースで、すべてあなたのマシン上で動きます。

                    @@ -176,7 +176,7 @@

                    正確なサイズの本物のテストファイルを生成

                    • - 27 + 28

                      種類の本物の形式、どれも対応するソフトウェアで開けます

                    • @@ -328,7 +328,7 @@

                      無人で動くテストスイートのために作られています

                      10485761バイトを要求すれば、ちょうどその大きさになります。形式が到達できないサイズは理由付きのエラーになり、サイズの違うファイルが作られることはありません。

                    • -

                      27種類の本物の形式

                      +

                      28種類の本物の形式

                      拡張子を付けただけのゼロ埋めではありません。生成したPNGは画像ビューアーで開け、DOCXはWordで開け、ZIPは展開できます。どれも出荷前に独立したリーダーで検証されます。

                    • diff --git a/web/public/ja/presets/empty-and-minimal/index.html b/web/public/ja/presets/empty-and-minimal/index.html index ebefc849..ed7af275 100644 --- a/web/public/ja/presets/empty-and-minimal/index.html +++ b/web/public/ja/presets/empty-and-minimal/index.html @@ -5,7 +5,7 @@ 全形式の最小の有効ファイルと空ファイル - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -176,10 +176,10 @@

                      セットには何が入っていますか。

                      - - - - + + + +
                      ファイル数29
                      レシピ内のターゲット数29
                      合計サイズ35 951 B
                      形式avif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                      ファイル数30
                      レシピ内のターゲット数30
                      合計サイズ39 263 B
                      形式avif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                      @@ -190,7 +190,7 @@

                      セットには何が入っていますか。

                      期待意味ファイル数 - acceptシステムはこのファイルを受け入れるべきです。27 + acceptシステムはこのファイルを受け入れるべきです。28 unspecifiedシステムのルール次第です。あなたが決め、実際に起きることが意図どおりかを確認してください。2 diff --git a/web/public/ko/corrupt-test-files/index.html b/web/public/ko/corrupt-test-files/index.html index 6bfaf520..cefdecf0 100644 --- a/web/public/ko/corrupt-test-files/index.html +++ b/web/public/ko/corrupt-test-files/index.html @@ -194,7 +194,7 @@

                      손상된 파일도 요청한 크기를 유지합니다

                      tfg generate --format png --size 2mb --damage zero-head --out ./out
                       tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

                      - 설정은 콜론 뒤에 씁니다. 옵션은 반복할 수 있고, 손상은 쓴 순서대로 적용됩니다. 27개 형식 모두에서 동작합니다. + 설정은 콜론 뒤에 씁니다. 옵션은 반복할 수 있고, 손상은 쓴 순서대로 적용됩니다. 28개 형식 모두에서 동작합니다.

                      diff --git a/web/public/ko/faq/index.html b/web/public/ko/faq/index.html index 6ed3c1e1..3898d959 100644 --- a/web/public/ko/faq/index.html +++ b/web/public/ko/faq/index.html @@ -205,7 +205,7 @@

                      자주 묻는 질문

                      다음에는 어떤 형식이 추가되나요?

                      -

                      7z, mp3, mp4입니다. 현재 27가지 형식이 처음부터 끝까지 동작합니다.

                      +

                      7z, mp3, mp4입니다. 현재 28가지 형식이 처음부터 끝까지 동작합니다.

                      @@ -276,7 +276,7 @@

                      자주 묻는 질문

                      { "@type": "Question", "name": "다음에는 어떤 형식이 추가되나요?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3, mp4입니다. 현재 27가지 형식이 처음부터 끝까지 동작합니다." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3, mp4입니다. 현재 28가지 형식이 처음부터 끝까지 동작합니다." } }, { "@type": "Question", diff --git a/web/public/ko/formats/index.html b/web/public/ko/formats/index.html index a71984f2..ad6a1a71 100644 --- a/web/public/ko/formats/index.html +++ b/web/public/ko/formats/index.html @@ -4,8 +4,8 @@ -지원하는 파일 형식 27종 - PDF, DOCX, PNG, ZIP 등 - +지원하는 파일 형식 28종 - PDF, DOCX, PNG, ZIP 등 + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
                      -

                      파일 형식 27가지, 모두 정확한 크기로 생성됩니다

                      +

                      파일 형식 28가지, 모두 정확한 크기로 생성됩니다

                      이들은 모두 해당 형식의 실제 파일입니다. 해당 소프트웨어에서 열리며 요청한 바이트 수와 정확히 같습니다. 확장자만 붙인 채운 0은 하나도 없습니다.

                      @@ -258,6 +258,14 @@

                      파일 형식 27가지, 모두 정확한 크기로 생성됩니다

                      full 해당 없음 + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -602,6 +610,41 @@

                      각 형식이 받는 설정

                      bom 참 또는 거짓 + + mp4 + width + 1 - 16384 픽셀 + + + + height + 1 - 16384 픽셀 + + + + duration + 10s와 같은 길이 + + + + change_interval + 10s와 같은 길이 + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + 10s와 같은 길이 + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/ko/index.html b/web/public/ko/index.html index 0abd0ca2..1e72225e 100644 --- a/web/public/ko/index.html +++ b/web/public/ko/index.html @@ -4,7 +4,7 @@ -테스트 파일 생성기 - 정확한 크기, 실제 형식 27종 +테스트 파일 생성기 - 정확한 크기, 실제 형식 28종 @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "QA를 위한 무료 오픈 소스 테스트 파일 생성기입니다. 27가지 형식의 정확한 크기의 실제 파일을 만들고, 테스트 대상 시스템이 각 파일에 어떻게 반응해야 하는지 알려 주는 매니페스트를 작성합니다.", + "description": "QA를 위한 무료 오픈 소스 테스트 파일 생성기입니다. 28가지 형식의 정확한 크기의 실제 파일을 만들고, 테스트 대상 시스템이 각 파일에 어떻게 반응해야 하는지 알려 주는 매니페스트를 작성합니다.", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

                      정확한 크기의 실제 테스트 파일을 만드세요

                      - PDF, PNG, DOCX, ZIP 등 모두 27가지 형식이며, 모두 해당 소프트웨어에서 열리는 실제 파일로 + PDF, PNG, DOCX, ZIP 등 모두 28가지 형식이며, 모두 해당 소프트웨어에서 열리는 실제 파일로 요청한 크기와 정확히 같습니다. 매 실행마다 애플리케이션이 각 파일을 어떻게 처리해야 하는지도 함께 기록합니다. 명령줄과 데스크톱 창, 무료 오픈 소스이며, 모두 여러분의 컴퓨터에서 동작합니다.

                      @@ -178,7 +178,7 @@

                      정확한 크기의 실제 테스트 파일을 만드세요

                      • - 27 + 28

                        가지 실제 형식, 각각 해당 소프트웨어에서 열립니다

                      • @@ -335,7 +335,7 @@

                        무인으로 실행되는 테스트 묶음을 위해 만들었습니다

                        10485761바이트를 요청하면 정확히 그 크기가 나옵니다. 형식이 도달할 수 없는 크기는 이유가 붙은 오류가 되며, 크기가 틀린 파일이 되는 일은 없습니다.

                      • -

                        실제 형식 27가지

                        +

                        실제 형식 28가지

                        확장자만 붙인 채운 0이 아닙니다. 생성된 PNG는 이미지 뷰어에서 열리고, DOCX는 Word에서 열리며, ZIP은 풀립니다. 모든 형식은 출시 전에 독립적인 리더로 검증됩니다.

                      • diff --git a/web/public/ko/presets/empty-and-minimal/index.html b/web/public/ko/presets/empty-and-minimal/index.html index 574c6da7..0e8310cc 100644 --- a/web/public/ko/presets/empty-and-minimal/index.html +++ b/web/public/ko/presets/empty-and-minimal/index.html @@ -5,7 +5,7 @@ 모든 형식의 가장 작은 유효 파일과 빈 파일 - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -177,10 +177,10 @@

                        세트에는 무엇이 들어 있나요?

                        - - - - + + + +
                        파일 수29
                        레시피의 타깃 수29
                        총 크기35 951 B
                        형식avif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                        파일 수30
                        레시피의 타깃 수30
                        총 크기39 263 B
                        형식avif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                        @@ -191,7 +191,7 @@

                        세트에는 무엇이 들어 있나요?

                        기대값의미파일 수 - accept시스템은 이 파일을 받아야 합니다.27 + accept시스템은 이 파일을 받아야 합니다.28 unspecified시스템의 규칙에 따라 다릅니다. 직접 결정한 뒤 실제 일어나는 일이 의도한 것인지 확인하세요.2 diff --git a/web/public/nl/beschadigde-testbestanden/index.html b/web/public/nl/beschadigde-testbestanden/index.html index 974bc3f6..774a3f3a 100644 --- a/web/public/nl/beschadigde-testbestanden/index.html +++ b/web/public/nl/beschadigde-testbestanden/index.html @@ -203,7 +203,7 @@

                        Een beschadigd bestand heeft nog steeds de grootte die je vroeg

                        Instellingen komen achter een dubbele punt. De optie mag herhaald worden, en de beschadigingen worden toegepast in de volgorde waarin je ze schrijft. Het werkt met elk van de - 27 formaten. + 28 formaten.

                        diff --git a/web/public/nl/formaten/index.html b/web/public/nl/formaten/index.html index 1cafb76c..6b0cdbd9 100644 --- a/web/public/nl/formaten/index.html +++ b/web/public/nl/formaten/index.html @@ -4,8 +4,8 @@ -27 ondersteunde bestandsformaten - PDF, DOCX, PNG, ZIP en meer - +28 ondersteunde bestandsformaten - PDF, DOCX, PNG, ZIP en meer + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
                        -

                        27 bestandsformaten, elk gegenereerd in een exacte grootte

                        +

                        28 bestandsformaten, elk gegenereerd in een exacte grootte

                        Elk daarvan is een echt bestand van dat formaat. Het opent in het programma waartoe het behoort en is precies het aantal bytes dat je vroeg. Geen enkele is opgevulde nullen met een @@ -260,6 +260,14 @@

                        27 bestandsformaten, elk gegenereerd in een exacte grootte

                        full niet van toepassing + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -611,6 +619,41 @@

                        Instellingen die elk formaat accepteert

                        bom waar of onwaar + + mp4 + width + 1 - 16384 pixels + + + + height + 1 - 16384 pixels + + + + duration + een duur zoals 10s + + + + change_interval + een duur zoals 10s + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + een duur zoals 10s + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/nl/index.html b/web/public/nl/index.html index a96f4e85..a23f1526 100644 --- a/web/public/nl/index.html +++ b/web/public/nl/index.html @@ -4,7 +4,7 @@ -Testbestanden genereren - exacte grootte, 27 echte formaten +Testbestanden genereren - exacte grootte, 28 echte formaten @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "Een gratis open source generator van testbestanden voor QA. Hij maakt echte bestanden in 27 formaten in elke exacte grootte en schrijft een manifest dat zegt hoe het geteste systeem op elk bestand moet reageren.", + "description": "Een gratis open source generator van testbestanden voor QA. Hij maakt echte bestanden in 28 formaten in elke exacte grootte en schrijft een manifest dat zegt hoe het geteste systeem op elk bestand moet reageren.", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

                        Genereer echte testbestanden in elke exacte grootte

                        - PDF, PNG, DOCX, ZIP - 27 formaten in totaal, en elk is een + PDF, PNG, DOCX, ZIP - 28 formaten in totaal, en elk is een echt bestand dat opent in het programma waartoe het behoort, in precies de grootte die je vroeg. Elke run schrijft ook op wat je applicatie met elk bestand moet doen. Opdrachtregel en bureaubladvenster, gratis en open source, volledig op je eigen machine. @@ -179,7 +179,7 @@

                        Genereer echte testbestanden in elke exacte grootte

                        • - 27 + 28

                          echte formaten, elk opent in het programma waartoe het behoort

                        • @@ -342,7 +342,7 @@

                          Gebouwd voor een suite die onbeheerd draait

                          Vraag 10485761 bytes en krijg precies dat. Een grootte die een formaat niet kan halen is een fout met een reden, nooit een bestand van de verkeerde grootte.

                        • -

                          27 echte formaten

                          +

                          28 echte formaten

                          Geen opgevulde nullen met een extensie. Een gegenereerde PNG opent in een afbeeldingsviewer, een DOCX opent in Word, een ZIP pakt uit. Elk wordt vóór levering gecontroleerd met onafhankelijke lezers.

                        • diff --git a/web/public/nl/presets/empty-and-minimal/index.html b/web/public/nl/presets/empty-and-minimal/index.html index 0d0d0f1d..7a58aaab 100644 --- a/web/public/nl/presets/empty-and-minimal/index.html +++ b/web/public/nl/presets/empty-and-minimal/index.html @@ -5,7 +5,7 @@ Kleinste geldige en lege testbestanden in elk formaat - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -179,10 +179,10 @@

                          Wat zit er in de set?

                          - - - - + + + +
                          Bestanden29
                          Targets in het recept29
                          Totale grootte35 951 B
                          Formatenavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                          Bestanden30
                          Targets in het recept30
                          Totale grootte39 263 B
                          Formatenavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                          @@ -193,7 +193,7 @@

                          Wat zit er in de set?

                          VerwachtBetekenisBestanden - acceptJe systeem moet het bestand aannemen.27 + acceptJe systeem moet het bestand aannemen.28 unspecifiedHet hangt af van de regels van je systeem. Jij beslist, en controleert daarna of wat er gebeurt is wat je bedoelde.2 diff --git a/web/public/nl/veelgestelde-vragen/index.html b/web/public/nl/veelgestelde-vragen/index.html index 6083aa57..7d240c3c 100644 --- a/web/public/nl/veelgestelde-vragen/index.html +++ b/web/public/nl/veelgestelde-vragen/index.html @@ -206,7 +206,7 @@

                          Veelgestelde vragen

                          Welke formaten komen er nog?

                          -

                          7z, mp3 en mp4. Vandaag werken 27 formaten van begin tot eind.

                          +

                          7z, mp3 en mp4. Vandaag werken 28 formaten van begin tot eind.

                          @@ -277,7 +277,7 @@

                          Veelgestelde vragen

                          { "@type": "Question", "name": "Welke formaten komen er nog?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 en mp4. Vandaag werken 27 formaten van begin tot eind." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 en mp4. Vandaag werken 28 formaten van begin tot eind." } }, { "@type": "Question", diff --git a/web/public/pl/faq/index.html b/web/public/pl/faq/index.html index de7dd61f..f8525c0f 100644 --- a/web/public/pl/faq/index.html +++ b/web/public/pl/faq/index.html @@ -206,7 +206,7 @@

                          Najczęstsze pytania

                          Jakie formaty są następne w kolejce?

                          -

                          7z, mp3 i mp4. Formatów działających dziś od początku do końca jest 27.

                          +

                          7z, mp3 i mp4. Formatów działających dziś od początku do końca jest 28.

                          @@ -277,7 +277,7 @@

                          Najczęstsze pytania

                          { "@type": "Question", "name": "Jakie formaty są następne w kolejce?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 i mp4. Formatów działających dziś od początku do końca jest 27." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 i mp4. Formatów działających dziś od początku do końca jest 28." } }, { "@type": "Question", diff --git a/web/public/pl/formaty/index.html b/web/public/pl/formaty/index.html index 8d7c0167..5da85b48 100644 --- a/web/public/pl/formaty/index.html +++ b/web/public/pl/formaty/index.html @@ -4,7 +4,7 @@ -27 formatów plików testowych - PDF, DOCX, PNG, ZIP +28 formatów plików testowych - PDF, DOCX, PNG, ZIP @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -144,7 +144,7 @@
                          -

                          27 formatów plików, każdy generowany o dokładnym rozmiarze

                          +

                          28 formatów plików, każdy generowany o dokładnym rozmiarze

                          Każdy z nich to prawdziwy plik tego formatu. Otwiera się w programie, do którego należy, i ma dokładnie tyle bajtów, ile zamówiłeś. Żaden nie jest zerami z doklejonym rozszerzeniem. @@ -259,6 +259,14 @@

                          27 formatów plików, każdy generowany o dokładnym rozmiarze

                          full nie dotyczy + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -609,6 +617,41 @@

                          Ustawienia, które przyjmuje każdy format

                          bom prawda albo fałsz + + mp4 + width + 1 - 16384 pikseli + + + + height + 1 - 16384 pikseli + + + + duration + czas trwania, na przykład 10s + + + + change_interval + czas trwania, na przykład 10s + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + czas trwania, na przykład 10s + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/pl/index.html b/web/public/pl/index.html index adceffb5..203412ae 100644 --- a/web/public/pl/index.html +++ b/web/public/pl/index.html @@ -4,7 +4,7 @@ -Generator plików testowych o zadanym rozmiarze - 27 formatów +Generator plików testowych o zadanym rozmiarze - 28 formatów @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "Darmowy generator plików testowych dla QA o otwartym kodzie. Tworzy prawdziwe pliki w 27 formatach o dokładnie zadanym rozmiarze i zapisuje manifest mówiący, jak testowany system ma na każdy z nich zareagować.", + "description": "Darmowy generator plików testowych dla QA o otwartym kodzie. Tworzy prawdziwe pliki w 28 formatach o dokładnie zadanym rozmiarze i zapisuje manifest mówiący, jak testowany system ma na każdy z nich zareagować.", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

                          Generuj pliki testowe o zadanym rozmiarze

                          - PDF, PNG, DOCX, ZIP - razem 27 formatów, a każdy to + PDF, PNG, DOCX, ZIP - razem 28 formatów, a każdy to prawdziwy plik, który otwiera się w programie, do którego należy, i ma dokładnie taki rozmiar, o jaki poprosisz. Każdy przebieg zapisuje też, co Twoja aplikacja ma z każdym plikiem zrobić. Wiersz poleceń i okno, darmowe i otwarte, działające wyłącznie na Twojej maszynie. @@ -179,7 +179,7 @@

                          Generuj pliki testowe o zadanym rozmiarze

                          • - 27 + 28

                            prawdziwych formatów, każdy otwiera się w programie, do którego należy

                          • @@ -342,7 +342,7 @@

                            Zbudowane pod zestaw, który chodzi bez nadzoru

                            Poproś o 10485761 bajtów i tyle dostaniesz. Rozmiar nieosiągalny dla formatu to błąd z powodem, nigdy plik o innym rozmiarze.

                          • -

                            27 prawdziwych formatów

                            +

                            28 prawdziwych formatów

                            Nie zera z doklejonym rozszerzeniem. Wygenerowany PNG otwiera się w przeglądarce obrazów, DOCX w Wordzie, a ZIP się rozpakowuje. Każdy format jest sprawdzany niezależnym czytnikiem, zanim trafi do wydania.

                          • diff --git a/web/public/pl/presety/empty-and-minimal/index.html b/web/public/pl/presety/empty-and-minimal/index.html index ca0c1deb..36da45e1 100644 --- a/web/public/pl/presety/empty-and-minimal/index.html +++ b/web/public/pl/presety/empty-and-minimal/index.html @@ -5,7 +5,7 @@ Najmniejsze poprawne i puste pliki w każdym formacie - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -178,10 +178,10 @@

                            Co jest w zestawie?

                            - - - - + + + +
                            Pliki29
                            Cele w przepisie29
                            Łączny rozmiar35 951 B
                            Formatyavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                            Pliki30
                            Cele w przepisie30
                            Łączny rozmiar39 263 B
                            Formatyavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                            @@ -192,7 +192,7 @@

                            Co jest w zestawie?

                            OczekiwanieZnaczeniePliki - acceptSystem powinien przyjąć plik.27 + acceptSystem powinien przyjąć plik.28 unspecifiedZależy od reguł Twojego systemu. Ty decydujesz, a potem sprawdzasz, czy dzieje się to, co zamierzałeś.2 diff --git a/web/public/pl/uszkodzone-pliki-testowe/index.html b/web/public/pl/uszkodzone-pliki-testowe/index.html index 2276caeb..28601dc4 100644 --- a/web/public/pl/uszkodzone-pliki-testowe/index.html +++ b/web/public/pl/uszkodzone-pliki-testowe/index.html @@ -202,7 +202,7 @@

                            Uszkodzony plik ma nadal rozmiar, o który prosiłeś

                            tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

                            Ustawienia podaje się po dwukropku. Flagę można powtarzać, a uszkodzenia są stosowane w kolejności, - w jakiej je zapiszesz. Działa z każdym z 27 formatów. + w jakiej je zapiszesz. Działa z każdym z 28 formatów.

                            diff --git a/web/public/presets/empty-and-minimal/index.html b/web/public/presets/empty-and-minimal/index.html index 24d2fa40..b21c70a9 100644 --- a/web/public/presets/empty-and-minimal/index.html +++ b/web/public/presets/empty-and-minimal/index.html @@ -5,7 +5,7 @@ Smallest Valid and Empty Test Files in Every Format - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -178,10 +178,10 @@

                            What is in the set?

                            - - - - + + + +
                            Files29
                            Targets in its recipe29
                            Total size35 951 B
                            Formatsavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                            Files30
                            Targets in its recipe30
                            Total size39 263 B
                            Formatsavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                            @@ -192,7 +192,7 @@

                            What is in the set?

                            ExpectedMeaningFiles - acceptYour system should take the file.27 + acceptYour system should take the file.28 unspecifiedIt depends on the rules of your system. You decide, then check that what happens is what you meant.2 diff --git a/web/public/pt-br/arquivos-de-teste-corrompidos/index.html b/web/public/pt-br/arquivos-de-teste-corrompidos/index.html index aa7303d8..67e3b379 100644 --- a/web/public/pt-br/arquivos-de-teste-corrompidos/index.html +++ b/web/public/pt-br/arquivos-de-teste-corrompidos/index.html @@ -201,7 +201,7 @@

                            Um arquivo danificado continua com o tamanho que você pediu

                            tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

                            As configurações vão depois de dois-pontos. A opção pode ser repetida, e os danos são aplicados na - ordem em que você os escreve. Funciona com cada um dos 27 formatos. + ordem em que você os escreve. Funciona com cada um dos 28 formatos.

                            diff --git a/web/public/pt-br/formatos/index.html b/web/public/pt-br/formatos/index.html index 1485bf15..935748b7 100644 --- a/web/public/pt-br/formatos/index.html +++ b/web/public/pt-br/formatos/index.html @@ -4,8 +4,8 @@ -27 formatos de arquivo - PDF, DOCX, PNG, ZIP e mais - +28 formatos de arquivo - PDF, DOCX, PNG, ZIP e mais + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
                            -

                            27 formatos de arquivo, cada um gerado em um tamanho exato

                            +

                            28 formatos de arquivo, cada um gerado em um tamanho exato

                            Cada um é um arquivo real daquele formato. Abre no programa a que pertence e tem exatamente o número de bytes que você pediu. Nenhum é preenchimento de zeros com uma extensão @@ -260,6 +260,14 @@

                            27 formatos de arquivo, cada um gerado em um tamanho exato

                            full não se aplica + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -610,6 +618,41 @@

                            Configurações que cada formato aceita

                            bom verdadeiro ou falso + + mp4 + width + 1 - 16384 pixels + + + + height + 1 - 16384 pixels + + + + duration + uma duração como 10s + + + + change_interval + uma duração como 10s + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + uma duração como 10s + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/pt-br/index.html b/web/public/pt-br/index.html index f751ee62..9f4a515e 100644 --- a/web/public/pt-br/index.html +++ b/web/public/pt-br/index.html @@ -4,7 +4,7 @@ -Gerador de arquivos de teste - tamanho exato, 27 formatos reais +Gerador de arquivos de teste - tamanho exato, 28 formatos reais @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "Um gerador gratuito e de código aberto de arquivos de teste para QA. Produz arquivos reais de 27 formatos no tamanho exato e escreve um manifesto que diz como o sistema sob teste deve reagir a cada um.", + "description": "Um gerador gratuito e de código aberto de arquivos de teste para QA. Produz arquivos reais de 28 formatos no tamanho exato e escreve um manifesto que diz como o sistema sob teste deve reagir a cada um.", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

                            Gere arquivos de teste reais no tamanho exato

                            - PDF, PNG, DOCX, ZIP - 27 formatos no total, e cada um é um + PDF, PNG, DOCX, ZIP - 28 formatos no total, e cada um é um arquivo real que abre no programa a que pertence, com exatamente o tamanho que você pediu. Cada execução também registra o que sua aplicação deve fazer com cada arquivo. Linha de comando e janela de desktop, gratuito e de código aberto, funcionando inteiramente na @@ -180,7 +180,7 @@

                            Gere arquivos de teste reais no tamanho exato

                            • - 27 + 28

                              formatos reais, cada um abre no programa a que pertence

                            • @@ -345,7 +345,7 @@

                              Feito para uma suíte que roda sem supervisão

                              Peça 10485761 bytes e receba exatamente isso. Um tamanho que um formato não alcança é um erro com um motivo, nunca um arquivo de tamanho errado.

                            • -

                              27 formatos reais

                              +

                              28 formatos reais

                              Não são zeros de preenchimento com uma extensão. Um PNG gerado abre em um visualizador de imagens, um DOCX abre no Word, um ZIP extrai. Cada um é verificado com leitores independentes antes de ser liberado.

                            • diff --git a/web/public/pt-br/perguntas-frequentes/index.html b/web/public/pt-br/perguntas-frequentes/index.html index 5e98aaee..0b443d1a 100644 --- a/web/public/pt-br/perguntas-frequentes/index.html +++ b/web/public/pt-br/perguntas-frequentes/index.html @@ -206,7 +206,7 @@

                              Perguntas frequentes

                              Quais formatos vêm a seguir?

                              -

                              7z, mp3 e mp4. Hoje 27 formatos funcionam de ponta a ponta.

                              +

                              7z, mp3 e mp4. Hoje 28 formatos funcionam de ponta a ponta.

                              @@ -277,7 +277,7 @@

                              Perguntas frequentes

                              { "@type": "Question", "name": "Quais formatos vêm a seguir?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 e mp4. Hoje 27 formatos funcionam de ponta a ponta." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 e mp4. Hoje 28 formatos funcionam de ponta a ponta." } }, { "@type": "Question", diff --git a/web/public/pt-br/presets/empty-and-minimal/index.html b/web/public/pt-br/presets/empty-and-minimal/index.html index ef549359..1b8e1918 100644 --- a/web/public/pt-br/presets/empty-and-minimal/index.html +++ b/web/public/pt-br/presets/empty-and-minimal/index.html @@ -5,7 +5,7 @@ Menores arquivos de teste válidos e vazios em cada formato - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -178,10 +178,10 @@

                              O que há no conjunto?

                              - - - - + + + +
                              Arquivos29
                              Targets na sua receita29
                              Tamanho total35 951 B
                              Formatosavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                              Arquivos30
                              Targets na sua receita30
                              Tamanho total39 263 B
                              Formatosavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                              @@ -192,7 +192,7 @@

                              O que há no conjunto?

                              EsperadoSignificadoArquivos - acceptSeu sistema deve aceitar o arquivo.27 + acceptSeu sistema deve aceitar o arquivo.28 unspecifiedDepende das regras do seu sistema. Você decide e depois confere se o que acontece é o que você queria.2 diff --git a/web/public/ro/fisiere-de-test-corupte/index.html b/web/public/ro/fisiere-de-test-corupte/index.html index 23615f23..ee9e7c68 100644 --- a/web/public/ro/fisiere-de-test-corupte/index.html +++ b/web/public/ro/fisiere-de-test-corupte/index.html @@ -202,7 +202,7 @@

                              Un fișier deteriorat are în continuare mărimea cerută

                              tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

                              Setările se scriu după două puncte. Opțiunea poate fi repetată, iar deteriorările se aplică în - ordinea în care le scrii. Funcționează cu fiecare dintre cele 27 formate. + ordinea în care le scrii. Funcționează cu fiecare dintre cele 28 formate.

                              diff --git a/web/public/ro/formate/index.html b/web/public/ro/formate/index.html index d06d9826..f324c679 100644 --- a/web/public/ro/formate/index.html +++ b/web/public/ro/formate/index.html @@ -4,8 +4,8 @@ -27 formate de fișiere - PDF, DOCX, PNG, ZIP și altele - +28 formate de fișiere - PDF, DOCX, PNG, ZIP și altele + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
                              -

                              27 formate de fișiere, fiecare generat la o dimensiune exactă

                              +

                              28 formate de fișiere, fiecare generat la o dimensiune exactă

                              Fiecare este un fișier real al acelui format. Se deschide în programul căruia îi aparține și are exact numărul de octeți pe care l-ai cerut. Niciunul nu e umplutură de zerouri cu @@ -260,6 +260,14 @@

                              27 formate de fișiere, fiecare generat la o dimensiune exactă

                              full nu se aplică + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -610,6 +618,41 @@

                              Setările pe care le acceptă fiecare format

                              bom adevărat sau fals + + mp4 + width + 1 - 16384 pixeli + + + + height + 1 - 16384 pixeli + + + + duration + o durată precum 10s + + + + change_interval + o durată precum 10s + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + o durată precum 10s + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/ro/index.html b/web/public/ro/index.html index 479f9a84..bdf01500 100644 --- a/web/public/ro/index.html +++ b/web/public/ro/index.html @@ -4,7 +4,7 @@ -Generator de fișiere de test - dimensiune exactă, 27 formate reale +Generator de fișiere de test - dimensiune exactă, 28 formate reale @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "Un generator gratuit și open source de fișiere de test pentru QA. Produce fișiere reale în 27 formate, la dimensiunea exactă, și scrie un manifest care spune cum trebuie să reacționeze sistemul testat la fiecare.", + "description": "Un generator gratuit și open source de fișiere de test pentru QA. Produce fișiere reale în 28 formate, la dimensiunea exactă, și scrie un manifest care spune cum trebuie să reacționeze sistemul testat la fiecare.", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

                              Generează fișiere de test reale la dimensiunea exactă

                              - PDF, PNG, DOCX, ZIP - 27 formate în total, și fiecare este un + PDF, PNG, DOCX, ZIP - 28 formate în total, și fiecare este un fișier real care se deschide în programul căruia îi aparține, la exact dimensiunea pe care ai cerut-o. Fiecare rulare notează și ce trebuie să facă aplicația ta cu fiecare fișier. Linie de comandă și fereastră desktop, gratuit și open source, funcționând în întregime @@ -180,7 +180,7 @@

                              Generează fișiere de test reale la dimensiunea exactă

                              • - 27 + 28

                                formate reale, fiecare se deschide în programul căruia îi aparține

                              • @@ -343,7 +343,7 @@

                                Construit pentru o suită care rulează nesupravegheată

                                Cere 10485761 de octeți și primești exact atât. O dimensiune pe care un format nu o poate atinge este o eroare cu un motiv, niciodată un fișier de dimensiune greșită.

                              • -

                                27 formate reale

                                +

                                28 formate reale

                                Nu zerouri de umplutură cu o extensie. Un PNG generat se deschide într-un vizualizator de imagini, un DOCX se deschide în Word, un ZIP se extrage. Fiecare este verificat cu cititori independenți înainte de livrare.

                              • diff --git a/web/public/ro/intrebari-frecvente/index.html b/web/public/ro/intrebari-frecvente/index.html index 0d90d175..b39d4dbf 100644 --- a/web/public/ro/intrebari-frecvente/index.html +++ b/web/public/ro/intrebari-frecvente/index.html @@ -206,7 +206,7 @@

                                Întrebări frecvente

                                Ce formate urmează?

                                -

                                7z, mp3 și mp4. Azi funcționează de la un capăt la altul 27 formate.

                                +

                                7z, mp3 și mp4. Azi funcționează de la un capăt la altul 28 formate.

                                @@ -277,7 +277,7 @@

                                Întrebări frecvente

                                { "@type": "Question", "name": "Ce formate urmează?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 și mp4. Azi funcționează de la un capăt la altul 27 formate." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 și mp4. Azi funcționează de la un capăt la altul 28 formate." } }, { "@type": "Question", diff --git a/web/public/ro/presetari/empty-and-minimal/index.html b/web/public/ro/presetari/empty-and-minimal/index.html index b87beb65..68a9e9af 100644 --- a/web/public/ro/presetari/empty-and-minimal/index.html +++ b/web/public/ro/presetari/empty-and-minimal/index.html @@ -5,7 +5,7 @@ Cele mai mici fișiere de test valide și goale, în fiecare format - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -179,10 +179,10 @@

                                Ce este în set?

                                - - - - + + + +
                                Fișiere29
                                Targeturi în rețeta lui29
                                Dimensiune totală35 951 B
                                Formateavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                                Fișiere30
                                Targeturi în rețeta lui30
                                Dimensiune totală39 263 B
                                Formateavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                                @@ -193,7 +193,7 @@

                                Ce este în set?

                                AșteptatSemnificațieFișiere - acceptSistemul tău trebuie să accepte fișierul.27 + acceptSistemul tău trebuie să accepte fișierul.28 unspecifiedDepinde de regulile sistemului tău. Tu decizi, apoi verifici că ce se întâmplă este ce ai vrut.2 diff --git a/web/public/ru/corrupt-test-files/index.html b/web/public/ru/corrupt-test-files/index.html index cdcd8d88..d2e87c4e 100644 --- a/web/public/ru/corrupt-test-files/index.html +++ b/web/public/ru/corrupt-test-files/index.html @@ -202,7 +202,7 @@

                                Повреждённый файл остаётся нужного разме tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

                                Настройки пишутся после двоеточия. Параметр можно повторять, а повреждения применяются в том - порядке, в каком вы их записали. Это работает с каждым из 27 форматов. + порядке, в каком вы их записали. Это работает с каждым из 28 форматов.

                                diff --git a/web/public/ru/faq/index.html b/web/public/ru/faq/index.html index 0df6fa07..7a235b9e 100644 --- a/web/public/ru/faq/index.html +++ b/web/public/ru/faq/index.html @@ -206,7 +206,7 @@

                                Часто задаваемые вопросы

                                Какие форматы появятся дальше?

                                -

                                7z, mp3 и mp4. Сегодня полностью работают 27 форматов.

                                +

                                7z, mp3 и mp4. Сегодня полностью работают 28 форматов.

                                @@ -277,7 +277,7 @@

                                Часто задаваемые вопросы

                                { "@type": "Question", "name": "Какие форматы появятся дальше?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 и mp4. Сегодня полностью работают 27 форматов." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 и mp4. Сегодня полностью работают 28 форматов." } }, { "@type": "Question", diff --git a/web/public/ru/formats/index.html b/web/public/ru/formats/index.html index 47684caa..c0ba76c0 100644 --- a/web/public/ru/formats/index.html +++ b/web/public/ru/formats/index.html @@ -4,8 +4,8 @@ -27 форматов файлов - PDF, DOCX, PNG, ZIP и другие - +28 форматов файлов - PDF, DOCX, PNG, ZIP и другие + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
                                -

                                27 форматов файлов, каждый создаётся точного размера

                                +

                                28 форматов файлов, каждый создаётся точного размера

                                Каждый из них - настоящий файл этого формата. Он открывается в своей программе и имеет ровно то число байт, которое вы запросили. Ни один не является нулями-заполнителями с @@ -260,6 +260,14 @@

                                27 форматов файлов, каждый создаётся точно full не применимо + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -609,6 +617,41 @@

                                Настройки, которые принимает каждый форм bom истина или ложь + + mp4 + width + 1 - 16384 пикселей + + + + height + 1 - 16384 пикселей + + + + duration + длительность, например 10s + + + + change_interval + длительность, например 10s + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + длительность, например 10s + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/ru/index.html b/web/public/ru/index.html index 57fcec8d..610f008a 100644 --- a/web/public/ru/index.html +++ b/web/public/ru/index.html @@ -4,7 +4,7 @@ -Генератор тестовых файлов для QA - точный размер, 27 форматов +Генератор тестовых файлов для QA - точный размер, 28 форматов @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "Бесплатный генератор тестовых файлов для QA с открытым кодом. Создаёт настоящие файлы в 27 форматах точного размера и записывает манифест с ожидаемой реакцией тестируемой системы на каждый из них.", + "description": "Бесплатный генератор тестовых файлов для QA с открытым кодом. Создаёт настоящие файлы в 28 форматах точного размера и записывает манифест с ожидаемой реакцией тестируемой системы на каждый из них.", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

                                Создавайте настоящие тестовые файлы точного размера

                                - PDF, PNG, DOCX, ZIP - всего 27 форматов, и каждый из них + PDF, PNG, DOCX, ZIP - всего 28 форматов, и каждый из них настоящий файл, который открывается в своей программе, точно того размера, который вы запросили. Каждый запуск ещё и записывает, что ваше приложение должно делать с каждым файлом. Командная строка и десктопное окно, бесплатно и с открытым кодом, всё работает на вашей @@ -180,7 +180,7 @@

                                Создавайте настоящие тестовые файлы точн
                                • - 27 + 28

                                  настоящих форматов, каждый открывается в своей программе

                                • @@ -343,7 +343,7 @@

                                  Создан для набора тестов, работающего без

                                  Запросите 10485761 байт и получите ровно столько. Размер, которого формат не может достичь, - это ошибка с причиной, а не файл неверного размера.

                                • -

                                  27 настоящих форматов

                                  +

                                  28 настоящих форматов

                                  Не нули-заполнители с расширением. Созданный PNG открывается в просмотрщике изображений, DOCX - в Word, ZIP распаковывается. Каждый формат проверяется независимыми читателями до выпуска.

                                • diff --git a/web/public/ru/presets/empty-and-minimal/index.html b/web/public/ru/presets/empty-and-minimal/index.html index 13eb66c3..490082d7 100644 --- a/web/public/ru/presets/empty-and-minimal/index.html +++ b/web/public/ru/presets/empty-and-minimal/index.html @@ -5,7 +5,7 @@ Минимальные корректные и пустые тестовые файлы в каждом формате - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -178,10 +178,10 @@

                                  Что входит в набор?

                                  - - - - + + + +
                                  Файлов29
                                  Целей в его рецепте29
                                  Общий размер35 951 B
                                  Форматыavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                                  Файлов30
                                  Целей в его рецепте30
                                  Общий размер39 263 B
                                  Форматыavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                                  @@ -192,7 +192,7 @@

                                  Что входит в набор?

                                  ОжидаетсяЗначениеФайлов - acceptВаша система должна принять файл.27 + acceptВаша система должна принять файл.28 unspecifiedЗависит от правил вашей системы. Вы решаете, а затем проверяете, что происходит именно то, что вы имели в виду.2 diff --git a/web/public/social.html b/web/public/social.html index 4c87c869..66859ac3 100644 --- a/web/public/social.html +++ b/web/public/social.html @@ -203,7 +203,7 @@

                                  Test sets,
                                  not just files.

                                  • Presets - ready test sets for size limits, file names, encodings and imports
                                  • Recipes - one YAML file, same bytes in every CI run
                                  • -
                                  • 27 formats with their own settings - pages, pixels, rows, archive contents
                                  • +
                                  • 28 formats with their own settings - pages, pixels, rows, archive contents

                                  Free and open source · works offline · Windows · macOS · Linux

                                diff --git a/web/public/th/corrupt-test-files/index.html b/web/public/th/corrupt-test-files/index.html index 21f6f9f6..52d78c1e 100644 --- a/web/public/th/corrupt-test-files/index.html +++ b/web/public/th/corrupt-test-files/index.html @@ -199,7 +199,7 @@

                                ไฟล์ที่เสียหายยังมีขนาดตา tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

                                การตั้งค่าเขียนหลังเครื่องหมายทวิภาค ตัวเลือกนี้ใส่ซ้ำได้ - และความเสียหายจะถูกนำมาใช้ตามลำดับที่คุณเขียน ใช้ได้กับทั้ง 27 รูปแบบ + และความเสียหายจะถูกนำมาใช้ตามลำดับที่คุณเขียน ใช้ได้กับทั้ง 28 รูปแบบ

                                diff --git a/web/public/th/faq/index.html b/web/public/th/faq/index.html index e00a3575..d56818f8 100644 --- a/web/public/th/faq/index.html +++ b/web/public/th/faq/index.html @@ -205,7 +205,7 @@

                                คำถามที่พบบ่อย

                                รูปแบบใดจะมาต่อไป

                                -

                                7z, mp3 และ mp4 ตอนนี้ 27 รูปแบบทำงานได้ครบตั้งแต่ต้นจนจบ

                                +

                                7z, mp3 และ mp4 ตอนนี้ 28 รูปแบบทำงานได้ครบตั้งแต่ต้นจนจบ

                                @@ -276,7 +276,7 @@

                                คำถามที่พบบ่อย

                                { "@type": "Question", "name": "รูปแบบใดจะมาต่อไป", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 และ mp4 ตอนนี้ 27 รูปแบบทำงานได้ครบตั้งแต่ต้นจนจบ" } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 และ mp4 ตอนนี้ 28 รูปแบบทำงานได้ครบตั้งแต่ต้นจนจบ" } }, { "@type": "Question", diff --git a/web/public/th/formats/index.html b/web/public/th/formats/index.html index 12e24590..e515e90f 100644 --- a/web/public/th/formats/index.html +++ b/web/public/th/formats/index.html @@ -4,8 +4,8 @@ -27 รูปแบบไฟล์ที่รองรับ - PDF, DOCX, PNG, ZIP และอื่นๆ - +28 รูปแบบไฟล์ที่รองรับ - PDF, DOCX, PNG, ZIP และอื่นๆ + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
                                -

                                27 รูปแบบไฟล์ แต่ละรูปแบบสร้างที่ขนาดแม่นยำ

                                +

                                28 รูปแบบไฟล์ แต่ละรูปแบบสร้างที่ขนาดแม่นยำ

                                ทุกรูปแบบเป็นไฟล์จริงของรูปแบบนั้น เปิดได้ในซอฟต์แวร์ของมัน และมีจำนวนไบต์ตรงตามที่คุณขอพอดี ไม่มีรูปแบบใดเป็นศูนย์เติมเต็มที่แปะนามสกุลเข้าไป @@ -259,6 +259,14 @@

                                27 รูปแบบไฟล์ แต่ละรูปแบบสร้ full ไม่เกี่ยวข้อง + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -607,6 +615,41 @@

                                การตั้งค่าที่แต่ละรูปแบบร bom จริงหรือเท็จ + + mp4 + width + 1 - 16384 พิกเซล + + + + height + 1 - 16384 พิกเซล + + + + duration + ระยะเวลา เช่น 10s + + + + change_interval + ระยะเวลา เช่น 10s + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + ระยะเวลา เช่น 10s + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/th/index.html b/web/public/th/index.html index 4492db41..5f0ef85b 100644 --- a/web/public/th/index.html +++ b/web/public/th/index.html @@ -4,7 +4,7 @@ -ตัวสร้างไฟล์ทดสอบสำหรับ QA - ขนาดแม่นยำ 27 รูปแบบจริง +ตัวสร้างไฟล์ทดสอบสำหรับ QA - ขนาดแม่นยำ 28 รูปแบบจริง @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "ตัวสร้างไฟล์ทดสอบสำหรับ QA ฟรีและโอเพนซอร์ส สร้างไฟล์จริงใน 27 รูปแบบด้วยขนาดแม่นยำ และเขียนแมนิเฟสต์ที่บอกว่าระบบที่ถูกทดสอบควรตอบสนองต่อแต่ละไฟล์อย่างไร", + "description": "ตัวสร้างไฟล์ทดสอบสำหรับ QA ฟรีและโอเพนซอร์ส สร้างไฟล์จริงใน 28 รูปแบบด้วยขนาดแม่นยำ และเขียนแมนิเฟสต์ที่บอกว่าระบบที่ถูกทดสอบควรตอบสนองต่อแต่ละไฟล์อย่างไร", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

                                สร้างไฟล์ทดสอบจริงที่ขนาดแม่นยำ

                                - PDF, PNG, DOCX, ZIP - รวม 27 รูปแบบ + PDF, PNG, DOCX, ZIP - รวม 28 รูปแบบ และทุกไฟล์เป็นไฟล์จริงที่เปิดได้ในซอฟต์แวร์ของมัน ด้วยขนาดตรงตามที่คุณขอพอดี ทุกครั้งที่รันยังจดด้วยว่าแอปพลิเคชันของคุณควรทำอย่างไรกับแต่ละไฟล์ มีทั้งบรรทัดคำสั่งและหน้าต่างเดสก์ท็อป ฟรีและโอเพนซอร์ส ทำงานทั้งหมดบนเครื่องของคุณ @@ -179,7 +179,7 @@

                                สร้างไฟล์ทดสอบจริงที่ขนาด
                                • - 27 + 28

                                  รูปแบบจริง แต่ละรูปแบบเปิดได้ในซอฟต์แวร์ของมัน

                                • @@ -341,7 +341,7 @@

                                  สร้างมาสำหรับชุดทดสอบที่ร

                                  ขอ 10485761 ไบต์ แล้วได้ตามนั้นพอดี ขนาดที่รูปแบบไปไม่ถึงคือข้อผิดพลาดที่มีเหตุผล ไม่ใช่ไฟล์ขนาดผิด

                                • -

                                  27 รูปแบบจริง

                                  +

                                  28 รูปแบบจริง

                                  ไม่ใช่ศูนย์ที่เติมเต็มพร้อมนามสกุล PNG ที่สร้างเปิดได้ในโปรแกรมดูภาพ DOCX เปิดได้ใน Word และ ZIP แตกไฟล์ได้ ทุกรูปแบบถูกตรวจด้วยตัวอ่านอิสระก่อนปล่อย

                                • diff --git a/web/public/th/presets/empty-and-minimal/index.html b/web/public/th/presets/empty-and-minimal/index.html index 5434a1da..5432b0ce 100644 --- a/web/public/th/presets/empty-and-minimal/index.html +++ b/web/public/th/presets/empty-and-minimal/index.html @@ -5,7 +5,7 @@ ไฟล์ทดสอบที่ถูกต้องเล็กที่สุดและไฟล์ว่างในทุกรูปแบบ - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -178,10 +178,10 @@

                                  ในชุดมีอะไร

                                  - - - - + + + +
                                  ไฟล์29
                                  target ในสูตรของมัน29
                                  ขนาดรวม35 951 B
                                  รูปแบบavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                                  ไฟล์30
                                  target ในสูตรของมัน30
                                  ขนาดรวม39 263 B
                                  รูปแบบavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                                  @@ -192,7 +192,7 @@

                                  ในชุดมีอะไร

                                  ที่คาดหวังความหมายไฟล์ - acceptระบบของคุณควรยอมรับไฟล์นี้27 + acceptระบบของคุณควรยอมรับไฟล์นี้28 unspecifiedขึ้นอยู่กับกฎของระบบของคุณ คุณเป็นผู้ตัดสิน แล้วตรวจสอบว่าสิ่งที่เกิดขึ้นตรงกับที่ตั้งใจ2 diff --git a/web/public/tr/bicimler/index.html b/web/public/tr/bicimler/index.html index f1796af0..995ef1b9 100644 --- a/web/public/tr/bicimler/index.html +++ b/web/public/tr/bicimler/index.html @@ -4,8 +4,8 @@ -27 desteklenen dosya biçimi - PDF, DOCX, PNG, ZIP ve daha fazlası - +28 desteklenen dosya biçimi - PDF, DOCX, PNG, ZIP ve daha fazlası + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
                                  -

                                  27 dosya biçimi, her biri tam boyutta üretilir

                                  +

                                  28 dosya biçimi, her biri tam boyutta üretilir

                                  Bunların her biri o biçimde gerçek bir dosyadır. Ait olduğu programda açılır ve tam istediğiniz bayt sayısındadır. Hiçbiri yapıştırılmış uzantılı dolgu sıfırları değildir. @@ -259,6 +259,14 @@

                                  27 dosya biçimi, her biri tam boyutta üretilir

                                  full uygulanamaz + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -609,6 +617,41 @@

                                  Her biçimin kabul ettiği ayarlar

                                  bom doğru veya yanlış + + mp4 + width + 1 - 16384 piksel + + + + height + 1 - 16384 piksel + + + + duration + 10s gibi bir süre + + + + change_interval + 10s gibi bir süre + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + 10s gibi bir süre + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/tr/bozuk-test-dosyalari/index.html b/web/public/tr/bozuk-test-dosyalari/index.html index 39ec3a79..b8bf1144 100644 --- a/web/public/tr/bozuk-test-dosyalari/index.html +++ b/web/public/tr/bozuk-test-dosyalari/index.html @@ -201,7 +201,7 @@

                                  Hasarlı bir dosya yine istediğiniz boyuttadır

                                  tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

                                  Ayarlar iki noktadan sonra yazılır. Seçenek tekrarlanabilir ve hasarlar yazdığınız sırayla - uygulanır. 27 biçimin her biriyle çalışır. + uygulanır. 28 biçimin her biriyle çalışır.

                                  diff --git a/web/public/tr/hazir-ayarlar/empty-and-minimal/index.html b/web/public/tr/hazir-ayarlar/empty-and-minimal/index.html index 116a365f..19a45fc5 100644 --- a/web/public/tr/hazir-ayarlar/empty-and-minimal/index.html +++ b/web/public/tr/hazir-ayarlar/empty-and-minimal/index.html @@ -5,7 +5,7 @@ Her biçimde en küçük geçerli ve boş test dosyaları - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -179,10 +179,10 @@

                                  Sette ne var?

                                  - - - - + + + +
                                  Dosya29
                                  Tarifindeki hedefler29
                                  Toplam boyut35 951 B
                                  Biçimleravif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                                  Dosya30
                                  Tarifindeki hedefler30
                                  Toplam boyut39 263 B
                                  Biçimleravif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                                  @@ -193,7 +193,7 @@

                                  Sette ne var?

                                  BeklenenAnlamıDosya - acceptSisteminiz dosyayı kabul etmeli.27 + acceptSisteminiz dosyayı kabul etmeli.28 unspecifiedSisteminizin kurallarına bağlı. Siz karar verirsiniz, sonra olanın amaçladığınız şey olduğunu doğrularsınız.2 diff --git a/web/public/tr/index.html b/web/public/tr/index.html index 086481fe..30e5113f 100644 --- a/web/public/tr/index.html +++ b/web/public/tr/index.html @@ -4,7 +4,7 @@ -QA için test dosyası oluşturucu - tam boyut, 27 gerçek biçim +QA için test dosyası oluşturucu - tam boyut, 28 gerçek biçim @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "QA için ücretsiz ve açık kaynaklı bir test dosyası oluşturucu. 27 biçimde tam boyutta gerçek dosyalar üretir ve test edilen sistemin her birine nasıl tepki vermesi gerektiğini söyleyen bir manifest yazar.", + "description": "QA için ücretsiz ve açık kaynaklı bir test dosyası oluşturucu. 28 biçimde tam boyutta gerçek dosyalar üretir ve test edilen sistemin her birine nasıl tepki vermesi gerektiğini söyleyen bir manifest yazar.", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

                                  Tam boyutta gerçek test dosyaları üretin

                                  - PDF, PNG, DOCX, ZIP - toplam 27 biçim ve her biri ait olduğu + PDF, PNG, DOCX, ZIP - toplam 28 biçim ve her biri ait olduğu programda açılan, tam istediğiniz boyutta gerçek bir dosya. Her çalıştırma ayrıca uygulamanızın her dosyayla ne yapması gerektiğini de yazar. Komut satırı ve masaüstü penceresi, ücretsiz ve açık kaynak, tamamen makinenizde çalışır. @@ -179,7 +179,7 @@

                                  Tam boyutta gerçek test dosyaları üretin

                                  • - 27 + 28

                                    gerçek biçim, her biri ait olduğu programda açılır

                                  • @@ -342,7 +342,7 @@

                                    Gözetimsiz çalışan bir test takımı için yapıldı

                                    10485761 bayt isteyin ve tam olarak onu alın. Bir biçimin ulaşamayacağı boyut nedeni olan bir hatadır, asla yanlış boyutta bir dosya değil.

                                  • -

                                    27 gerçek biçim

                                    +

                                    28 gerçek biçim

                                    Uzantılı dolgu sıfırları değil. Üretilmiş bir PNG resim görüntüleyicide, bir DOCX Word'de açılır, bir ZIP çıkarılır. Her biri yayımlanmadan önce bağımsız okuyucularla doğrulanır.

                                  • diff --git a/web/public/tr/sss/index.html b/web/public/tr/sss/index.html index cf9929fc..6fc70dd7 100644 --- a/web/public/tr/sss/index.html +++ b/web/public/tr/sss/index.html @@ -205,7 +205,7 @@

                                    Sık sorulan sorular

                                    Sırada hangi biçimler var?

                                    -

                                    7z, mp3 ve mp4. Bugün 27 biçim uçtan uca çalışıyor.

                                    +

                                    7z, mp3 ve mp4. Bugün 28 biçim uçtan uca çalışıyor.

                                    @@ -276,7 +276,7 @@

                                    Sık sorulan sorular

                                    { "@type": "Question", "name": "Sırada hangi biçimler var?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 ve mp4. Bugün 27 biçim uçtan uca çalışıyor." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 ve mp4. Bugün 28 biçim uçtan uca çalışıyor." } }, { "@type": "Question", diff --git a/web/public/uk/corrupt-test-files/index.html b/web/public/uk/corrupt-test-files/index.html index 34645867..d8bcdaee 100644 --- a/web/public/uk/corrupt-test-files/index.html +++ b/web/public/uk/corrupt-test-files/index.html @@ -202,7 +202,7 @@

                                    Пошкоджений файл лишається потрібного ро tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

                                    Налаштування пишуться після двокрапки. Параметр можна повторювати, а пошкодження застосовуються в - тому порядку, в якому ви їх записали. Це працює з кожним із 27 форматів. + тому порядку, в якому ви їх записали. Це працює з кожним із 28 форматів.

                                    diff --git a/web/public/uk/faq/index.html b/web/public/uk/faq/index.html index beeab99a..55afef03 100644 --- a/web/public/uk/faq/index.html +++ b/web/public/uk/faq/index.html @@ -206,7 +206,7 @@

                                    Часті запитання

                                    Які формати з'являться далі?

                                    -

                                    7z, mp3 і mp4. Сьогодні повністю працюють 27 форматів.

                                    +

                                    7z, mp3 і mp4. Сьогодні повністю працюють 28 форматів.

                                    @@ -277,7 +277,7 @@

                                    Часті запитання

                                    { "@type": "Question", "name": "Які формати з\u0027являться далі?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 і mp4. Сьогодні повністю працюють 27 форматів." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 і mp4. Сьогодні повністю працюють 28 форматів." } }, { "@type": "Question", diff --git a/web/public/uk/formats/index.html b/web/public/uk/formats/index.html index 9f22d304..1c77e57b 100644 --- a/web/public/uk/formats/index.html +++ b/web/public/uk/formats/index.html @@ -4,8 +4,8 @@ -27 форматів файлів - PDF, DOCX, PNG, ZIP та інші - +28 форматів файлів - PDF, DOCX, PNG, ZIP та інші + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
                                    -

                                    27 форматів файлів, кожен створюється точного розміру

                                    +

                                    28 форматів файлів, кожен створюється точного розміру

                                    Кожен із них - справжній файл цього формату. Він відкривається у своїй програмі та має рівно стільки байтів, скільки ви запросили. Жоден не є нулями-заповнювачами з приклеєним @@ -260,6 +260,14 @@

                                    27 форматів файлів, кожен створюється точн full не застосовується + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -609,6 +617,41 @@

                                    Налаштування, які приймає кожен формат

                                    bom істина або хибність + + mp4 + width + 1 - 16384 пікселів + + + + height + 1 - 16384 пікселів + + + + duration + тривалість, наприклад 10s + + + + change_interval + тривалість, наприклад 10s + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + тривалість, наприклад 10s + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/uk/index.html b/web/public/uk/index.html index d278d71a..7e2a1c2f 100644 --- a/web/public/uk/index.html +++ b/web/public/uk/index.html @@ -4,7 +4,7 @@ -Генератор тестових файлів для QA - точний розмір, 27 форматів +Генератор тестових файлів для QA - точний розмір, 28 форматів @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "Безкоштовний генератор тестових файлів для QA з відкритим кодом. Створює справжні файли у 27 форматах точного розміру й записує маніфест з очікуваною реакцією тестованої системи на кожен із них.", + "description": "Безкоштовний генератор тестових файлів для QA з відкритим кодом. Створює справжні файли у 28 форматах точного розміру й записує маніфест з очікуваною реакцією тестованої системи на кожен із них.", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

                                    Створюйте справжні тестові файли точного розміру

                                    - PDF, PNG, DOCX, ZIP - усього 27 форматів, і кожен із них - + PDF, PNG, DOCX, ZIP - усього 28 форматів, і кожен із них - справжній файл, що відкривається у своїй програмі, точно того розміру, який ви запросили. Кожен запуск ще й записує, що ваш застосунок має робити з кожним файлом. Командний рядок і десктопне вікно, безкоштовно та з відкритим кодом, усе працює на вашій машині. @@ -179,7 +179,7 @@

                                    Створюйте справжні тестові файли точного
                                    • - 27 + 28

                                      справжніх форматів, кожен відкривається у своїй програмі

                                    • @@ -342,7 +342,7 @@

                                      Створений для набору тестів, що працює бе

                                      Запросіть 10485761 байт і отримайте рівно стільки. Розмір, якого формат не може досягти, - це помилка з причиною, а не файл неправильного розміру.

                                    • -

                                      27 справжніх форматів

                                      +

                                      28 справжніх форматів

                                      Не нулі-заповнювачі з розширенням. Створений PNG відкривається в переглядачі зображень, DOCX - у Word, ZIP розпаковується. Кожен формат перевіряється незалежними читачами до випуску.

                                    • diff --git a/web/public/uk/presets/empty-and-minimal/index.html b/web/public/uk/presets/empty-and-minimal/index.html index 41abd8a3..e7511b13 100644 --- a/web/public/uk/presets/empty-and-minimal/index.html +++ b/web/public/uk/presets/empty-and-minimal/index.html @@ -5,7 +5,7 @@ Мінімальні коректні та порожні тестові файли в кожному форматі - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -178,10 +178,10 @@

                                      Що входить у набір?

                                      - - - - + + + +
                                      Файлів29
                                      Цілей у його рецепті29
                                      Загальний розмір35 951 B
                                      Форматиavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                                      Файлів30
                                      Цілей у його рецепті30
                                      Загальний розмір39 263 B
                                      Форматиavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                                      @@ -192,7 +192,7 @@

                                      Що входить у набір?

                                      ОчікуєтьсяЗначенняФайлів - acceptВаша система має прийняти файл.27 + acceptВаша система має прийняти файл.28 unspecifiedЗалежить від правил вашої системи. Ви вирішуєте, а потім перевіряєте, що відбувається саме те, що ви мали на увазі.2 diff --git a/web/public/vi/dinh-dang/index.html b/web/public/vi/dinh-dang/index.html index 0e50f334..914213cd 100644 --- a/web/public/vi/dinh-dang/index.html +++ b/web/public/vi/dinh-dang/index.html @@ -4,8 +4,8 @@ -27 định dạng tệp được hỗ trợ - PDF, DOCX, PNG, ZIP và hơn nữa - +28 định dạng tệp được hỗ trợ - PDF, DOCX, PNG, ZIP và hơn nữa + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
                                      -

                                      27 định dạng tệp, mỗi định dạng được tạo ở kích thước chính xác

                                      +

                                      28 định dạng tệp, mỗi định dạng được tạo ở kích thước chính xác

                                      Mỗi cái là một tệp thật của định dạng đó. Nó mở được bằng phần mềm của nó và có đúng số byte bạn đã yêu cầu. Không cái nào là số không độn với phần mở rộng dán vào. @@ -259,6 +259,14 @@

                                      27 định dạng tệp, mỗi định dạng được tạo ở kích thư full không áp dụng + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -607,6 +615,41 @@

                                      Các thiết lập mỗi định dạng nhận

                                      bom đúng hoặc sai + + mp4 + width + 1 - 16384 pixel + + + + height + 1 - 16384 pixel + + + + duration + thời lượng như 10s + + + + change_interval + thời lượng như 10s + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + thời lượng như 10s + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/vi/faq/index.html b/web/public/vi/faq/index.html index 8054bbca..b69606dc 100644 --- a/web/public/vi/faq/index.html +++ b/web/public/vi/faq/index.html @@ -206,7 +206,7 @@

                                      Câu hỏi thường gặp

                                      Những định dạng nào sắp có?

                                      -

                                      7z, mp3 và mp4. Hôm nay có 27 định dạng hoạt động trọn vẹn từ đầu đến cuối.

                                      +

                                      7z, mp3 và mp4. Hôm nay có 28 định dạng hoạt động trọn vẹn từ đầu đến cuối.

                                      @@ -277,7 +277,7 @@

                                      Câu hỏi thường gặp

                                      { "@type": "Question", "name": "Những định dạng nào sắp có?", - "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 và mp4. Hôm nay có 27 định dạng hoạt động trọn vẹn từ đầu đến cuối." } + "acceptedAnswer": { "@type": "Answer", "text": "7z, mp3 và mp4. Hôm nay có 28 định dạng hoạt động trọn vẹn từ đầu đến cuối." } }, { "@type": "Question", diff --git a/web/public/vi/index.html b/web/public/vi/index.html index fc0e2474..a295533b 100644 --- a/web/public/vi/index.html +++ b/web/public/vi/index.html @@ -4,7 +4,7 @@ -Trình tạo tệp kiểm thử cho QA - kích thước chính xác, 27 định dạng +Trình tạo tệp kiểm thử cho QA - kích thước chính xác, 28 định dạng @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "Trình tạo tệp kiểm thử miễn phí, mã nguồn mở cho QA. Nó tạo tệp thật ở 27 định dạng, đúng kích thước, và ghi manifest nêu cách hệ thống được kiểm thử cần phản ứng với từng tệp.", + "description": "Trình tạo tệp kiểm thử miễn phí, mã nguồn mở cho QA. Nó tạo tệp thật ở 28 định dạng, đúng kích thước, và ghi manifest nêu cách hệ thống được kiểm thử cần phản ứng với từng tệp.", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

                                      Tạo tệp kiểm thử thật đúng kích thước

                                      - PDF, PNG, DOCX, ZIP - tổng cộng 27 định dạng, và mỗi tệp là + PDF, PNG, DOCX, ZIP - tổng cộng 28 định dạng, và mỗi tệp là tệp thật mở được bằng phần mềm của nó, ở đúng kích thước bạn yêu cầu. Mỗi lần chạy còn ghi lại ứng dụng của bạn cần làm gì với từng tệp. Dòng lệnh và cửa sổ desktop, miễn phí, mã nguồn mở, chạy hoàn toàn trên máy của bạn. @@ -179,7 +179,7 @@

                                      Tạo tệp kiểm thử thật đúng kích thước

                                      • - 27 + 28

                                        định dạng thật, mỗi định dạng mở được bằng phần mềm của nó

                                      • @@ -341,7 +341,7 @@

                                        Làm cho bộ kiểm thử chạy không cần người trông

                                        Yêu cầu 10485761 byte và nhận đúng như vậy. Kích thước mà định dạng không thể đạt là một lỗi có lý do, không bao giờ là tệp sai kích thước.

                                      • -

                                        27 định dạng thật

                                        +

                                        28 định dạng thật

                                        Không phải số không độn kèm một phần mở rộng. PNG được tạo mở trong trình xem ảnh, DOCX mở trong Word, ZIP giải nén được. Mỗi định dạng được kiểm tra bằng các trình đọc độc lập trước khi phát hành.

                                      • diff --git a/web/public/vi/preset/empty-and-minimal/index.html b/web/public/vi/preset/empty-and-minimal/index.html index 42f8f265..255bb40e 100644 --- a/web/public/vi/preset/empty-and-minimal/index.html +++ b/web/public/vi/preset/empty-and-minimal/index.html @@ -5,7 +5,7 @@ Tệp kiểm thử hợp lệ nhỏ nhất và tệp rỗng ở mọi định dạng - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -178,10 +178,10 @@

                                        Trong bộ có gì?

                                        - - - - + + + +
                                        Tệp29
                                        Target trong công thức của nó29
                                        Tổng kích thước35 951 B
                                        Định dạngavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                                        Tệp30
                                        Target trong công thức của nó30
                                        Tổng kích thước39 263 B
                                        Định dạngavif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                                        @@ -192,7 +192,7 @@

                                        Trong bộ có gì?

                                        Mong đợiÝ nghĩaTệp - acceptHệ thống của bạn nên nhận tệp.27 + acceptHệ thống của bạn nên nhận tệp.28 unspecifiedTùy vào quy tắc của hệ thống bạn. Bạn quyết định, rồi kiểm tra điều xảy ra có đúng ý bạn không.2 diff --git a/web/public/vi/tep-kiem-thu-bi-hong/index.html b/web/public/vi/tep-kiem-thu-bi-hong/index.html index eee07034..e572942f 100644 --- a/web/public/vi/tep-kiem-thu-bi-hong/index.html +++ b/web/public/vi/tep-kiem-thu-bi-hong/index.html @@ -199,7 +199,7 @@

                                        Tệp bị hỏng vẫn có kích thước bạn đã yêu cầu

                                        tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

                                        Cài đặt viết sau dấu hai chấm. Tùy chọn có thể lặp lại, và các hỏng hóc được áp dụng theo thứ tự bạn - viết. Nó dùng được với từng định dạng trong 27 định dạng. + viết. Nó dùng được với từng định dạng trong 28 định dạng.

                                        diff --git a/web/public/zh-hans/corrupt-test-files/index.html b/web/public/zh-hans/corrupt-test-files/index.html index fbf5d029..3993288c 100644 --- a/web/public/zh-hans/corrupt-test-files/index.html +++ b/web/public/zh-hans/corrupt-test-files/index.html @@ -189,7 +189,7 @@

                                        损坏的文件仍然是你要求的大小

                                        tfg generate --format png --size 2mb --damage zero-head --out ./out
                                         tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

                                        - 设置写在冒号之后。该选项可以重复,损坏按你写下的顺序依次应用。它适用于全部 27 种格式。 + 设置写在冒号之后。该选项可以重复,损坏按你写下的顺序依次应用。它适用于全部 28 种格式。

                                        diff --git a/web/public/zh-hans/faq/index.html b/web/public/zh-hans/faq/index.html index d918e953..e7cb150d 100644 --- a/web/public/zh-hans/faq/index.html +++ b/web/public/zh-hans/faq/index.html @@ -204,7 +204,7 @@

                                        常见问题

                                        接下来会支持哪些格式?

                                        -

                                        7z、mp3 和 mp4。目前有 27 种格式可以端到端使用。

                                        +

                                        7z、mp3 和 mp4。目前有 28 种格式可以端到端使用。

                                        @@ -275,7 +275,7 @@

                                        常见问题

                                        { "@type": "Question", "name": "接下来会支持哪些格式?", - "acceptedAnswer": { "@type": "Answer", "text": "7z、mp3 和 mp4。目前有 27 种格式可以端到端使用。" } + "acceptedAnswer": { "@type": "Answer", "text": "7z、mp3 和 mp4。目前有 28 种格式可以端到端使用。" } }, { "@type": "Question", diff --git a/web/public/zh-hans/formats/index.html b/web/public/zh-hans/formats/index.html index 0405c559..9b0d9333 100644 --- a/web/public/zh-hans/formats/index.html +++ b/web/public/zh-hans/formats/index.html @@ -4,8 +4,8 @@ -27 种支持的文件格式 - PDF、DOCX、PNG、ZIP 等 - +28 种支持的文件格式 - PDF、DOCX、PNG、ZIP 等 + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
                                        -

                                        27 种文件格式,每一种都按精确大小生成

                                        +

                                        28 种文件格式,每一种都按精确大小生成

                                        它们每一个都是该格式的真实文件。它能在对应软件中打开,字节数恰好等于你的要求。没有一个是粘上扩展名的填充零。

                                        @@ -258,6 +258,14 @@

                                        27 种文件格式,每一种都按精确大小生成

                                        full 不适用 + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -601,6 +609,41 @@

                                        每种格式接受的设置

                                        bom 真或假 + + mp4 + width + 1 - 16384 像素 + + + + height + 1 - 16384 像素 + + + + duration + 形如 10s 的时长 + + + + change_interval + 形如 10s 的时长 + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + 形如 10s 的时长 + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/zh-hans/index.html b/web/public/zh-hans/index.html index 48af13ee..c0be8ffa 100644 --- a/web/public/zh-hans/index.html +++ b/web/public/zh-hans/index.html @@ -4,7 +4,7 @@ -测试文件生成器 - 精确大小,27 种真实格式 +测试文件生成器 - 精确大小,28 种真实格式 @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "面向 QA 的免费开源测试文件生成器。它生成 27 种格式、大小精确的真实文件,并写出说明被测系统应如何响应每个文件的清单。", + "description": "面向 QA 的免费开源测试文件生成器。它生成 28 种格式、大小精确的真实文件,并写出说明被测系统应如何响应每个文件的清单。", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

                                        生成大小精确的真实测试文件

                                        - PDF、PNG、DOCX、ZIP,共 27 + PDF、PNG、DOCX、ZIP,共 28 种格式,每一种都是能在对应软件中打开的真实文件,大小恰好等于你的要求。每次运行还会写下你的应用应该如何处理每个文件。命令行加桌面窗口,免费开源,完全在你的机器上运行。

                                        @@ -177,7 +177,7 @@

                                        生成大小精确的真实测试文件

                                        • - 27 + 28

                                          种真实格式,每一种都能在对应软件中打开

                                        • @@ -332,7 +332,7 @@

                                          为无人值守的测试套件而生

                                          请求 10485761 字节,就得到恰好这么多。格式无法达到的大小会得到带原因的错误,绝不会得到大小错误的文件。

                                        • -

                                          27 种真实格式

                                          +

                                          28 种真实格式

                                          不是带扩展名的填充零。生成的 PNG 能在图片查看器中打开,DOCX 能在 Word 中打开,ZIP 能解压。每种格式在发布前都经过独立读取器验证。

                                        • diff --git a/web/public/zh-hans/presets/empty-and-minimal/index.html b/web/public/zh-hans/presets/empty-and-minimal/index.html index 2e59287f..2daf3eb4 100644 --- a/web/public/zh-hans/presets/empty-and-minimal/index.html +++ b/web/public/zh-hans/presets/empty-and-minimal/index.html @@ -5,7 +5,7 @@ 各格式最小的合法文件与空文件 - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -177,10 +177,10 @@

                                          集合里有什么?

                                          - - - - + + + +
                                          文件数29
                                          其配方中的 target 数29
                                          总大小35 951 B
                                          格式avif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                                          文件数30
                                          其配方中的 target 数30
                                          总大小39 263 B
                                          格式avif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                                          @@ -191,7 +191,7 @@

                                          集合里有什么?

                                          预期含义文件数 - accept你的系统应该接受这个文件。27 + accept你的系统应该接受这个文件。28 unspecified取决于你的系统规则。由你决定,然后检查实际发生的是否符合你的本意。2 diff --git a/web/public/zh-hant/corrupt-test-files/index.html b/web/public/zh-hant/corrupt-test-files/index.html index 269c57e9..ade1f7e4 100644 --- a/web/public/zh-hant/corrupt-test-files/index.html +++ b/web/public/zh-hant/corrupt-test-files/index.html @@ -189,7 +189,7 @@

                                          損毀的檔案仍然是你要求的大小

                                          tfg generate --format png --size 2mb --damage zero-head --out ./out
                                           tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken

                                          - 設定寫在冒號之後。該選項可以重複,損毀依你寫下的順序逐一套用。它適用於全部 27 種格式。 + 設定寫在冒號之後。該選項可以重複,損毀依你寫下的順序逐一套用。它適用於全部 28 種格式。

                                          diff --git a/web/public/zh-hant/faq/index.html b/web/public/zh-hant/faq/index.html index 6a5b81f9..bba950b8 100644 --- a/web/public/zh-hant/faq/index.html +++ b/web/public/zh-hant/faq/index.html @@ -204,7 +204,7 @@

                                          常見問題

                                          接下來會支援哪些格式?

                                          -

                                          7z、mp3 與 mp4。目前有 27 種格式可以端到端使用。

                                          +

                                          7z、mp3 與 mp4。目前有 28 種格式可以端到端使用。

                                          @@ -275,7 +275,7 @@

                                          常見問題

                                          { "@type": "Question", "name": "接下來會支援哪些格式?", - "acceptedAnswer": { "@type": "Answer", "text": "7z、mp3 與 mp4。目前有 27 種格式可以端到端使用。" } + "acceptedAnswer": { "@type": "Answer", "text": "7z、mp3 與 mp4。目前有 28 種格式可以端到端使用。" } }, { "@type": "Question", diff --git a/web/public/zh-hant/formats/index.html b/web/public/zh-hant/formats/index.html index 98f6ab20..12ea84c9 100644 --- a/web/public/zh-hant/formats/index.html +++ b/web/public/zh-hant/formats/index.html @@ -4,8 +4,8 @@ -27 種支援的檔案格式 - PDF、DOCX、PNG、ZIP 等 - +28 種支援的檔案格式 - PDF、DOCX、PNG、ZIP 等 + @@ -33,8 +33,8 @@ - - + + @@ -62,8 +62,8 @@ - - + + @@ -144,7 +144,7 @@
                                          -

                                          27 種檔案格式,每一種都以精確大小產生

                                          +

                                          28 種檔案格式,每一種都以精確大小產生

                                          它們每一個都是該格式的真實檔案。它能在對應軟體中開啟,位元組數恰好等於你的要求。沒有一個是黏上副檔名的填充零。

                                          @@ -258,6 +258,14 @@

                                          27 種檔案格式,每一種都以精確大小產生

                                          full 不適用 + + mp4 + MP4 video + .mp4 + 3312 + full + libaom + pdf Portable Document Format @@ -601,6 +609,41 @@

                                          每種格式接受的設定

                                          bom 真或假 + + mp4 + width + 1 - 16384 像素 + + + + height + 1 - 16384 像素 + + + + duration + 形如 10s 的時長 + + + + change_interval + 形如 10s 的時長 + + + + frame_rate + 1, 5, 10, 12, 15, 24, 25, 30, 48, 50, 60 + + + + keyframe_interval + 形如 10s 的時長 + + + + quality + 1 - 100 + pdf pages diff --git a/web/public/zh-hant/index.html b/web/public/zh-hant/index.html index bf0bcfb6..5186d084 100644 --- a/web/public/zh-hant/index.html +++ b/web/public/zh-hant/index.html @@ -4,7 +4,7 @@ -測試檔案產生器 - 精確大小,27 種真實格式 +測試檔案產生器 - 精確大小,28 種真實格式 @@ -33,7 +33,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ "@type": "SoftwareApplication", "name": "Testing Files Generator", "alternateName": "tfg", - "description": "專為 QA 設計的免費開源測試檔案產生器。它產生 27 種格式、大小精確的真實檔案,並寫出說明受測系統應如何回應每個檔案的清單。", + "description": "專為 QA 設計的免費開源測試檔案產生器。它產生 28 種格式、大小精確的真實檔案,並寫出說明受測系統應如何回應每個檔案的清單。", "url": "https:\/\/testingfilesgenerator.donislawdev.com/", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "Software Testing", @@ -157,7 +157,7 @@

                                          產生大小精確的真實測試檔案

                                          - PDF、PNG、DOCX、ZIP,共 27 + PDF、PNG、DOCX、ZIP,共 28 種格式,每一種都是能在對應軟體中開啟的真實檔案,大小恰好等於你的要求。每次執行還會寫下你的應用程式應該如何處理每個檔案。命令列加桌面視窗,免費開源,完全在你的電腦上執行。

                                          @@ -177,7 +177,7 @@

                                          產生大小精確的真實測試檔案

                                          • - 27 + 28

                                            種真實格式,每一種都能在對應軟體中開啟

                                          • @@ -332,7 +332,7 @@

                                            為無人看管的測試套件而生

                                            要求 10485761 位元組,就得到恰好這麼多。格式無法達到的大小會得到附上原因的錯誤,絕不會得到大小錯誤的檔案。

                                          • -

                                            27 種真實格式

                                            +

                                            28 種真實格式

                                            不是帶副檔名的填充零。產生的 PNG 能在圖片檢視器中開啟,DOCX 能在 Word 中開啟,ZIP 能解壓縮。每種格式在發布前都經過獨立讀取器驗證。

                                          • diff --git a/web/public/zh-hant/presets/empty-and-minimal/index.html b/web/public/zh-hant/presets/empty-and-minimal/index.html index c14edcc8..080bb41a 100644 --- a/web/public/zh-hant/presets/empty-and-minimal/index.html +++ b/web/public/zh-hant/presets/empty-and-minimal/index.html @@ -5,7 +5,7 @@ 各格式最小的合法檔案與空檔案 - + @@ -34,7 +34,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -177,10 +177,10 @@

                                            組合裡有什麼?

                                            - - - - + + + +
                                            檔案數29
                                            其配方中的 target 數29
                                            總大小35 951 B
                                            格式avif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                                            檔案數30
                                            其配方中的 target 數30
                                            總大小39 263 B
                                            格式avif, bmp, csv, docx, gif, html, ico, jpg, json, jxl, log, md, mp4, pdf, png, pptx, svg, targz, tiff, toml, txt, wav, webm, webp, xlsx, xml, yaml, zip
                                            @@ -191,7 +191,7 @@

                                            組合裡有什麼?

                                            預期意義檔案數 - accept你的系統應該接受這個檔案。27 + accept你的系統應該接受這個檔案。28 unspecified取決於你的系統規則。由你決定,然後檢查實際發生的是否符合你的本意。2 diff --git a/web/social-preview.sha256 b/web/social-preview.sha256 index 73023be3..79d1762e 100644 --- a/web/social-preview.sha256 +++ b/web/social-preview.sha256 @@ -1 +1 @@ -b45021553dde4061b7fb15b03c91e21011c0048b6d900e91f80eab1f1369ed67 +e049c19b8faa2c6b287c26d0b7f54a58922f4c2d68a8fcb20090501620aa16c5