Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
8c0955dd41
commit
3cf1c699df
120
frontend/package-lock.json
generated
120
frontend/package-lock.json
generated
|
@ -3872,6 +3872,25 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/arraybuffer.prototype.slice": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz",
|
||||
"integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==",
|
||||
"dependencies": {
|
||||
"array-buffer-byte-length": "^1.0.0",
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.2.0",
|
||||
"get-intrinsic": "^1.2.1",
|
||||
"is-array-buffer": "^3.0.2",
|
||||
"is-shared-array-buffer": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/asap": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||
|
@ -5619,9 +5638,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.461",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.461.tgz",
|
||||
"integrity": "sha512-1JkvV2sgEGTDXjdsaQCeSwYYuhLRphRpc+g6EHTFELJXEiznLt3/0pZ9JuAOQ5p2rI3YxKTbivtvajirIfhrEQ=="
|
||||
"version": "1.4.463",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.463.tgz",
|
||||
"integrity": "sha512-fT3hvdUWLjDbaTGzyOjng/CQhQJSQP8ThO3XZAoaxHvHo2kUXiRQVMj9M235l8uDFiNPsPa6KHT1p3RaR6ugRw=="
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
|
@ -5717,11 +5736,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/es-abstract": {
|
||||
"version": "1.21.3",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.3.tgz",
|
||||
"integrity": "sha512-ZU4miiY1j3sGPFLJ34VJXEqhpmL+HGByCinGHv4HC+Fxl2fI2Z4yR6tl0mORnDr6PA8eihWo4LmSWDbvhALckg==",
|
||||
"version": "1.22.1",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz",
|
||||
"integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==",
|
||||
"dependencies": {
|
||||
"array-buffer-byte-length": "^1.0.0",
|
||||
"arraybuffer.prototype.slice": "^1.0.1",
|
||||
"available-typed-arrays": "^1.0.5",
|
||||
"call-bind": "^1.0.2",
|
||||
"es-set-tostringtag": "^2.0.1",
|
||||
|
@ -5748,10 +5768,13 @@
|
|||
"object-keys": "^1.1.1",
|
||||
"object.assign": "^4.1.4",
|
||||
"regexp.prototype.flags": "^1.5.0",
|
||||
"safe-array-concat": "^1.0.0",
|
||||
"safe-regex-test": "^1.0.0",
|
||||
"string.prototype.trim": "^1.2.7",
|
||||
"string.prototype.trimend": "^1.0.6",
|
||||
"string.prototype.trimstart": "^1.0.6",
|
||||
"typed-array-buffer": "^1.0.0",
|
||||
"typed-array-byte-length": "^1.0.0",
|
||||
"typed-array-byte-offset": "^1.0.0",
|
||||
"typed-array-length": "^1.0.4",
|
||||
"unbox-primitive": "^1.0.2",
|
||||
|
@ -8454,15 +8477,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/is-typed-array": {
|
||||
"version": "1.1.10",
|
||||
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz",
|
||||
"integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz",
|
||||
"integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==",
|
||||
"dependencies": {
|
||||
"available-typed-arrays": "^1.0.5",
|
||||
"call-bind": "^1.0.2",
|
||||
"for-each": "^0.3.3",
|
||||
"gopd": "^1.0.1",
|
||||
"has-tostringtag": "^1.0.0"
|
||||
"which-typed-array": "^1.1.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
@ -8493,6 +8512,11 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
||||
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
|
||||
},
|
||||
"node_modules/isbinaryfile": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz",
|
||||
|
@ -12153,6 +12177,23 @@
|
|||
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
|
||||
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="
|
||||
},
|
||||
"node_modules/safe-array-concat": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz",
|
||||
"integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"get-intrinsic": "^1.2.0",
|
||||
"has-symbols": "^1.0.3",
|
||||
"isarray": "^2.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
|
@ -13113,9 +13154,9 @@
|
|||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.19.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.19.0.tgz",
|
||||
"integrity": "sha512-JpcpGOQLOXm2jsomozdMDpd5f8ZHh1rR48OFgWUH3QsyZcfPgv2qDCYbcDEAYNd4OZRj2bWYKpwdll/udZCk/Q==",
|
||||
"version": "5.19.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.19.1.tgz",
|
||||
"integrity": "sha512-27hxBUVdV6GoNg1pKQ7Z5cbR6V9txPVyBA+FQw3BaZ1Wuzvztce5p156DaP0NVZNrMZZ+6iG9Syf7WgMNKDg2Q==",
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.8.2",
|
||||
|
@ -13413,6 +13454,36 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/typed-array-buffer": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz",
|
||||
"integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"get-intrinsic": "^1.2.1",
|
||||
"is-typed-array": "^1.1.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/typed-array-byte-length": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz",
|
||||
"integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"for-each": "^0.3.3",
|
||||
"has-proto": "^1.0.1",
|
||||
"is-typed-array": "^1.1.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/typed-array-byte-offset": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz",
|
||||
|
@ -14077,9 +14148,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.88.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.1.tgz",
|
||||
"integrity": "sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==",
|
||||
"version": "5.88.2",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz",
|
||||
"integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==",
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.3",
|
||||
"@types/estree": "^1.0.0",
|
||||
|
@ -14431,16 +14502,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/which-typed-array": {
|
||||
"version": "1.1.10",
|
||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.10.tgz",
|
||||
"integrity": "sha512-uxoA5vLUfRPdjCuJ1h5LlYdmTLbYfums398v3WLkM+i/Wltl2/XyZpQWKbN++ck5L64SR/grOHqtXCUKmlZPNA==",
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz",
|
||||
"integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==",
|
||||
"dependencies": {
|
||||
"available-typed-arrays": "^1.0.5",
|
||||
"call-bind": "^1.0.2",
|
||||
"for-each": "^0.3.3",
|
||||
"gopd": "^1.0.1",
|
||||
"has-tostringtag": "^1.0.0",
|
||||
"is-typed-array": "^1.1.10"
|
||||
"has-tostringtag": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
|
|
@ -408,7 +408,7 @@ export default {
|
|||
for (let i = 0; i < thumbs.length; i++) {
|
||||
let t = thumbs[i];
|
||||
|
||||
result.push({"text": t.w + 'x' + t.h, "value": t.size});
|
||||
result.push({"text": t.w + ' × ' + t.h, "value": t.size});
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
@ -238,48 +238,14 @@ const clientConfig = {
|
|||
},
|
||||
],
|
||||
thumbs: [
|
||||
{
|
||||
size: "fit_720",
|
||||
use: "Mobile, TV",
|
||||
w: 720,
|
||||
h: 720,
|
||||
},
|
||||
{
|
||||
size: "fit_1280",
|
||||
use: "Mobile, HD Ready TV",
|
||||
w: 1280,
|
||||
h: 1024,
|
||||
},
|
||||
{
|
||||
size: "fit_1920",
|
||||
use: "Mobile, Full HD TV",
|
||||
w: 1920,
|
||||
h: 1200,
|
||||
},
|
||||
{
|
||||
size: "fit_2048",
|
||||
use: "Tablets, Cinema 2K",
|
||||
w: 2048,
|
||||
h: 2048,
|
||||
},
|
||||
{
|
||||
size: "fit_2560",
|
||||
use: "Quad HD, Retina Display",
|
||||
w: 2560,
|
||||
h: 1600,
|
||||
},
|
||||
{
|
||||
size: "fit_4096",
|
||||
use: "Ultra HD, Retina 4K",
|
||||
w: 4096,
|
||||
h: 4096,
|
||||
},
|
||||
{
|
||||
size: "fit_7680",
|
||||
use: "8K Ultra HD 2, Retina 6K",
|
||||
w: 7680,
|
||||
h: 4320,
|
||||
},
|
||||
{ size: "fit_720", usage: "SD TV, Mobile", w: 720, h: 720 },
|
||||
{ size: "fit_1280", usage: "HD TV, SXGA", w: 1280, h: 1024 },
|
||||
{ size: "fit_1920", usage: "Full HD", w: 1920, h: 1200 },
|
||||
{ size: "fit_2048", usage: "DCI 2K, Tablets", w: 2048, h: 2048 },
|
||||
{ size: "fit_2560", usage: "Quad HD, Notebooks", w: 2560, h: 1600 },
|
||||
{ size: "fit_3840", usage: "4K Ultra HD", w: 3840, h: 2400 },
|
||||
{ size: "fit_4096", usage: "DCI 4K, Retina 4K", w: 4096, h: 4096 },
|
||||
{ size: "fit_7680", usage: "8K Ultra HD 2", w: 7680, h: 4320 },
|
||||
],
|
||||
status: "unregistered",
|
||||
mapKey: "D9ve6edlcVR2mEsNvCXa",
|
||||
|
|
|
@ -14,6 +14,8 @@ var ShowCommand = cli.Command{
|
|||
ShowConfigYamlCommand,
|
||||
ShowSearchFiltersCommand,
|
||||
ShowFileFormatsCommand,
|
||||
ShowThumbSizesCommand,
|
||||
ShowVideoSizesCommand,
|
||||
ShowMetadataCommand,
|
||||
},
|
||||
}
|
||||
|
|
29
internal/commands/show_thumb_sizes.go
Normal file
29
internal/commands/show_thumb_sizes.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/report"
|
||||
)
|
||||
|
||||
// ShowThumbSizesCommand configures the command name, flags, and action.
|
||||
var ShowThumbSizesCommand = cli.Command{
|
||||
Name: "thumb-sizes",
|
||||
Usage: "Displays supported standard thumbnail sizes",
|
||||
Flags: report.CliFlags,
|
||||
Action: showThumbSizesAction,
|
||||
}
|
||||
|
||||
// showThumbSizesAction displays supported standard thumbnail sizes.
|
||||
func showThumbSizesAction(ctx *cli.Context) error {
|
||||
rows, cols := thumb.Report(thumb.Sizes.All(), false)
|
||||
|
||||
result, err := report.RenderFormat(rows, cols, report.CliFormat(ctx))
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
return err
|
||||
}
|
29
internal/commands/show_video_sizes.go
Normal file
29
internal/commands/show_video_sizes.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/report"
|
||||
)
|
||||
|
||||
// ShowVideoSizesCommand configures the command name, flags, and action.
|
||||
var ShowVideoSizesCommand = cli.Command{
|
||||
Name: "video-sizes",
|
||||
Usage: "Displays supported standard video sizes",
|
||||
Flags: report.CliFlags,
|
||||
Action: showVideoSizesAction,
|
||||
}
|
||||
|
||||
// showVideoSizesAction displays supported standard video sizes.
|
||||
func showVideoSizesAction(ctx *cli.Context) error {
|
||||
rows, cols := thumb.Report(thumb.VideoSizes, true)
|
||||
|
||||
result, err := report.RenderFormat(rows, cols, report.CliFormat(ctx))
|
||||
|
||||
fmt.Println(result)
|
||||
|
||||
return err
|
||||
}
|
|
@ -73,7 +73,7 @@ func init() {
|
|||
t := thumb.Sizes[name]
|
||||
|
||||
if t.Public {
|
||||
Thumbs = append(Thumbs, ThumbSize{Size: string(name), Use: t.Use, Width: t.Width, Height: t.Height})
|
||||
Thumbs = append(Thumbs, ThumbSize{Size: string(name), Usage: t.Usage, Width: t.Width, Height: t.Height})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/ffmpeg"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
)
|
||||
|
||||
// FFmpegBin returns the ffmpeg executable file name.
|
||||
|
@ -25,6 +26,11 @@ func (c *Config) FFmpegEncoder() ffmpeg.AvcEncoder {
|
|||
return ffmpeg.FindEncoder(c.options.FFmpegEncoder)
|
||||
}
|
||||
|
||||
// FFmpegSize returns the maximum ffmpeg video encoding size in pixels (720-7680).
|
||||
func (c *Config) FFmpegSize() int {
|
||||
return thumb.VideoSize(c.options.FFmpegSize).Width
|
||||
}
|
||||
|
||||
// FFmpegBitrate returns the ffmpeg bitrate limit in MBit/s.
|
||||
func (c *Config) FFmpegBitrate() int {
|
||||
switch {
|
||||
|
@ -37,18 +43,6 @@ func (c *Config) FFmpegBitrate() int {
|
|||
}
|
||||
}
|
||||
|
||||
// FFmpegResolution returns the ffmpeg resolution limit in pixel height. Goes from 144p to 8k.
|
||||
func (c *Config) FFmpegResolution() int {
|
||||
switch {
|
||||
case c.options.FFmpegResolution <= 0:
|
||||
return 4096
|
||||
case c.options.FFmpegResolution >= 8192:
|
||||
return 8192
|
||||
default:
|
||||
return c.options.FFmpegResolution
|
||||
}
|
||||
}
|
||||
|
||||
// FFmpegBitrateExceeded tests if the ffmpeg bitrate limit is exceeded.
|
||||
func (c *Config) FFmpegBitrateExceeded(mbit float64) bool {
|
||||
if mbit <= 0 {
|
||||
|
@ -82,12 +76,12 @@ func (c *Config) FFmpegMapAudio() string {
|
|||
func (c *Config) FFmpegOptions(encoder ffmpeg.AvcEncoder, bitrate string) (ffmpeg.Options, error) {
|
||||
// Transcode all other formats with FFmpeg.
|
||||
opt := ffmpeg.Options{
|
||||
Bin: c.FFmpegBin(),
|
||||
Encoder: encoder,
|
||||
Bitrate: bitrate,
|
||||
MapVideo: c.FFmpegMapVideo(),
|
||||
MapAudio: c.FFmpegMapAudio(),
|
||||
Resolution: fmt.Sprintf("%v", c.FFmpegResolution()),
|
||||
Bin: c.FFmpegBin(),
|
||||
Encoder: encoder,
|
||||
Size: c.FFmpegSize(),
|
||||
Bitrate: bitrate,
|
||||
MapVideo: c.FFmpegMapVideo(),
|
||||
MapAudio: c.FFmpegMapAudio(),
|
||||
}
|
||||
|
||||
// Check
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/ffmpeg"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -43,15 +44,30 @@ func TestConfig_FFmpegBitrate(t *testing.T) {
|
|||
assert.Equal(t, 800, c.FFmpegBitrate())
|
||||
}
|
||||
|
||||
func TestConfig_FFmpegResolution(t *testing.T) {
|
||||
func TestConfig_FFmpegSize(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
assert.Equal(t, 4096, c.FFmpegResolution())
|
||||
assert.Equal(t, 3840, c.FFmpegSize())
|
||||
|
||||
c.options.FFmpegResolution = 1920
|
||||
assert.Equal(t, 1920, c.FFmpegResolution())
|
||||
c.options.FFmpegSize = 0
|
||||
assert.Equal(t, 3840, c.FFmpegSize())
|
||||
|
||||
c.options.FFmpegResolution = 8640
|
||||
assert.Equal(t, 8192, c.FFmpegResolution())
|
||||
c.options.FFmpegSize = -1
|
||||
assert.Equal(t, 7680, c.FFmpegSize())
|
||||
|
||||
c.options.FFmpegSize = 10
|
||||
assert.Equal(t, 720, c.FFmpegSize())
|
||||
|
||||
c.options.FFmpegSize = 720
|
||||
assert.Equal(t, 720, c.FFmpegSize())
|
||||
|
||||
c.options.FFmpegSize = 1920
|
||||
assert.Equal(t, 1920, c.FFmpegSize())
|
||||
|
||||
c.options.FFmpegSize = 4000
|
||||
assert.Equal(t, 3840, c.FFmpegSize())
|
||||
|
||||
c.options.FFmpegSize = 8640
|
||||
assert.Equal(t, thumb.Sizes[thumb.Fit7680].Width, c.FFmpegSize())
|
||||
}
|
||||
|
||||
func TestConfig_FFmpegBitrateExceeded(t *testing.T) {
|
||||
|
|
|
@ -580,16 +580,16 @@ var Flags = CliFlags{
|
|||
EnvVar: EnvVar("FFMPEG_ENCODER"),
|
||||
}}, {
|
||||
Flag: cli.IntFlag{
|
||||
Name: "ffmpeg-bitrate, vb",
|
||||
Usage: "maximum FFmpeg encoding `BITRATE` (Mbit/s)",
|
||||
Value: 50,
|
||||
EnvVar: EnvVar("FFMPEG_BITRATE"),
|
||||
Name: "ffmpeg-size, vs",
|
||||
Usage: "maximum FFmpeg encoding size in `PIXELS` (720-7680)",
|
||||
Value: thumb.Sizes[thumb.Fit3840].Width,
|
||||
EnvVar: EnvVar("FFMPEG_SIZE"),
|
||||
}}, {
|
||||
Flag: cli.IntFlag{
|
||||
Name: "ffmpeg-resolution",
|
||||
Usage: "maximum FFmpeg encoding `RESOLUTION` (height)",
|
||||
Value: 4096,
|
||||
EnvVar: EnvVar("FFMPEG_RESOLUTION"),
|
||||
Name: "ffmpeg-bitrate, vb",
|
||||
Usage: "maximum FFmpeg video `BITRATE` in Mbit/s",
|
||||
Value: 50,
|
||||
EnvVar: EnvVar("FFMPEG_BITRATE"),
|
||||
}}, {
|
||||
Flag: cli.StringFlag{
|
||||
Name: "ffmpeg-map-video",
|
||||
|
|
|
@ -129,8 +129,8 @@ type Options struct {
|
|||
SipsBlacklist string `yaml:"SipsBlacklist" json:"-" flag:"sips-blacklist"`
|
||||
FFmpegBin string `yaml:"FFmpegBin" json:"-" flag:"ffmpeg-bin"`
|
||||
FFmpegEncoder string `yaml:"FFmpegEncoder" json:"FFmpegEncoder" flag:"ffmpeg-encoder"`
|
||||
FFmpegSize int `yaml:"FFmpegSize" json:"FFmpegSize" flag:"ffmpeg-size"`
|
||||
FFmpegBitrate int `yaml:"FFmpegBitrate" json:"FFmpegBitrate" flag:"ffmpeg-bitrate"`
|
||||
FFmpegResolution int `yaml:"FFmpegResolution" json:"FFmpegResolution" flag:"ffmpeg-resolution"`
|
||||
FFmpegMapVideo string `yaml:"FFmpegMapVideo" json:"FFmpegMapVideo" flag:"ffmpeg-map-video"`
|
||||
FFmpegMapAudio string `yaml:"FFmpegMapAudio" json:"FFmpegMapAudio" flag:"ffmpeg-map-audio"`
|
||||
ExifToolBin string `yaml:"ExifToolBin" json:"-" flag:"exiftool-bin"`
|
||||
|
|
|
@ -184,8 +184,8 @@ func (c *Config) Report() (rows [][]string, cols []string) {
|
|||
{"sips-blacklist", c.SipsBlacklist()},
|
||||
{"ffmpeg-bin", c.FFmpegBin()},
|
||||
{"ffmpeg-encoder", c.FFmpegEncoder().String()},
|
||||
{"ffmpeg-size", fmt.Sprintf("%d", c.FFmpegSize())},
|
||||
{"ffmpeg-bitrate", fmt.Sprintf("%d", c.FFmpegBitrate())},
|
||||
{"ffmpeg-resolution", fmt.Sprintf("%d", c.FFmpegResolution())},
|
||||
{"ffmpeg-map-video", c.FFmpegMapVideo()},
|
||||
{"ffmpeg-map-audio", c.FFmpegMapAudio()},
|
||||
{"exiftool-bin", c.ExifToolBin()},
|
||||
|
|
|
@ -3,7 +3,7 @@ package config
|
|||
// ThumbSize represents thumbnail info for use in client apps.
|
||||
type ThumbSize struct {
|
||||
Size string `json:"size"`
|
||||
Use string `json:"use"`
|
||||
Usage string `json:"usage"`
|
||||
Width int `json:"w"`
|
||||
Height int `json:"h"`
|
||||
}
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
package ffmpeg
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Options represents transcoding options.
|
||||
type Options struct {
|
||||
Bin string
|
||||
Encoder AvcEncoder
|
||||
Bitrate string
|
||||
MapVideo string
|
||||
MapAudio string
|
||||
Resolution string
|
||||
Bin string
|
||||
Encoder AvcEncoder
|
||||
Size int
|
||||
Bitrate string
|
||||
MapVideo string
|
||||
MapAudio string
|
||||
}
|
||||
|
||||
// VideoFilter returns the FFmpeg video filter string based on the size limit in pixels and the pixel format.
|
||||
func (o Options) VideoFilter(format PixelFormat) string {
|
||||
// scale specifies the FFmpeg downscale filter, see http://trac.ffmpeg.org/wiki/Scaling.
|
||||
if format == "" {
|
||||
return fmt.Sprintf("scale='if(gte(iw,ih), min(%d, iw), -2):if(gte(iw,ih), -2, min(%d, ih))'", o.Size, o.Size)
|
||||
} else {
|
||||
return fmt.Sprintf("scale='if(gte(iw,ih), min(%d, iw), -2):if(gte(iw,ih), -2, min(%d, ih))',format=%s", o.Size, o.Size, format)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,6 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
return nil, false, fmt.Errorf("empty output filename")
|
||||
}
|
||||
|
||||
scale := "scale='if(gte(iw,ih), min(" + opt.Resolution + ", iw), -2):if(gte(iw,ih), -2, min(" + opt.Resolution + ", ih))'"
|
||||
|
||||
// Don't transcode more than one video at the same time.
|
||||
useMutex = true
|
||||
|
||||
|
@ -26,7 +24,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
opt.Bin,
|
||||
"-i", fileName,
|
||||
"-movflags", "faststart",
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-pix_fmt", FormatYUV420P.String(),
|
||||
"-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
||||
"-f", "mp4",
|
||||
"-y",
|
||||
|
@ -44,13 +42,12 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
switch opt.Encoder {
|
||||
case IntelEncoder:
|
||||
// ffmpeg -hide_banner -h encoder=h264_qsv
|
||||
format := "format=rgb32"
|
||||
result = exec.Command(
|
||||
opt.Bin,
|
||||
"-qsv_device", "/dev/dri/renderD128",
|
||||
"-i", fileName,
|
||||
"-c:a", "aac",
|
||||
"-vf", scale+", "+format+"",
|
||||
"-vf", opt.VideoFilter(FormatRGB32),
|
||||
"-c:v", opt.Encoder.String(),
|
||||
"-map", opt.MapVideo,
|
||||
"-map", opt.MapAudio,
|
||||
|
@ -65,7 +62,6 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
|
||||
case AppleEncoder:
|
||||
// ffmpeg -hide_banner -h encoder=h264_videotoolbox
|
||||
format := "format=yuv420p"
|
||||
result = exec.Command(
|
||||
opt.Bin,
|
||||
"-i", fileName,
|
||||
|
@ -73,7 +69,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
"-map", opt.MapVideo,
|
||||
"-map", opt.MapAudio,
|
||||
"-c:a", "aac",
|
||||
"-vf", scale+", "+format+"",
|
||||
"-vf", opt.VideoFilter(FormatYUV420P),
|
||||
"-profile", "high",
|
||||
"-level", "51",
|
||||
"-vsync", "vfr",
|
||||
|
@ -85,13 +81,12 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
)
|
||||
|
||||
case VAAPIEncoder:
|
||||
format := "format=nv12,hwupload"
|
||||
result = exec.Command(
|
||||
opt.Bin,
|
||||
"-hwaccel", "vaapi",
|
||||
"-i", fileName,
|
||||
"-c:a", "aac",
|
||||
"-vf", scale+", "+format+"",
|
||||
"-vf", opt.VideoFilter(FormatNV12),
|
||||
"-c:v", opt.Encoder.String(),
|
||||
"-map", opt.MapVideo,
|
||||
"-map", opt.MapAudio,
|
||||
|
@ -105,12 +100,11 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
|
||||
case NvidiaEncoder:
|
||||
// ffmpeg -hide_banner -h encoder=h264_nvenc
|
||||
format := "format=yuv420p"
|
||||
result = exec.Command(
|
||||
opt.Bin,
|
||||
"-hwaccel", "auto",
|
||||
"-i", fileName,
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-pix_fmt", FormatYUV420P.String(),
|
||||
"-c:v", opt.Encoder.String(),
|
||||
"-map", opt.MapVideo,
|
||||
"-map", opt.MapAudio,
|
||||
|
@ -118,7 +112,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
"-preset", "15",
|
||||
"-pixel_format", "yuv420p",
|
||||
"-gpu", "any",
|
||||
"-vf", scale+", "+format+"",
|
||||
"-vf", opt.VideoFilter(FormatYUV420P),
|
||||
"-rc:v", "constqp",
|
||||
"-cq", "0",
|
||||
"-tune", "2",
|
||||
|
@ -134,7 +128,6 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
|
||||
case Video4LinuxEncoder:
|
||||
// ffmpeg -hide_banner -h encoder=h264_v4l2m2m
|
||||
format := "format=yuv420p"
|
||||
result = exec.Command(
|
||||
opt.Bin,
|
||||
"-i", fileName,
|
||||
|
@ -142,7 +135,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
"-map", opt.MapVideo,
|
||||
"-map", opt.MapAudio,
|
||||
"-c:a", "aac",
|
||||
"-vf", scale+", "+format+"",
|
||||
"-vf", opt.VideoFilter(FormatYUV420P),
|
||||
"-num_output_buffers", "72",
|
||||
"-num_capture_buffers", "64",
|
||||
"-max_muxing_queue_size", "1024",
|
||||
|
@ -156,7 +149,6 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
)
|
||||
|
||||
default:
|
||||
format := "format=yuv420p"
|
||||
result = exec.Command(
|
||||
opt.Bin,
|
||||
"-i", fileName,
|
||||
|
@ -164,7 +156,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
"-map", opt.MapVideo,
|
||||
"-map", opt.MapAudio,
|
||||
"-c:a", "aac",
|
||||
"-vf", scale+", "+format+"",
|
||||
"-vf", opt.VideoFilter(FormatYUV420P),
|
||||
"-max_muxing_queue_size", "1024",
|
||||
"-crf", "23",
|
||||
"-vsync", "vfr",
|
||||
|
|
16
internal/ffmpeg/format.go
Normal file
16
internal/ffmpeg/format.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package ffmpeg
|
||||
|
||||
// PixelFormat represents a standard pixel format.
|
||||
type PixelFormat string
|
||||
|
||||
// String returns the pixel format as string.
|
||||
func (f PixelFormat) String() string {
|
||||
return string(f)
|
||||
}
|
||||
|
||||
// Standard pixel formats.
|
||||
const (
|
||||
FormatYUV420P PixelFormat = "yuv420p"
|
||||
FormatRGB32 PixelFormat = "rgb32"
|
||||
FormatNV12 PixelFormat = "nv12,hwupload"
|
||||
)
|
|
@ -2,9 +2,9 @@ package thumb
|
|||
|
||||
import "image"
|
||||
|
||||
// Fitted contains only "fit" cropped thumbnail sizes from largest to smallest.
|
||||
// FitSizes contains "fit" cropped thumbnail sizes from largest to smallest.
|
||||
// Best for the viewer as proportional resizing maintains the aspect ratio.
|
||||
var Fitted = []Size{
|
||||
var FitSizes = SizeList{
|
||||
Sizes[Fit7680],
|
||||
Sizes[Fit4096],
|
||||
Sizes[Fit3840],
|
||||
|
@ -17,15 +17,15 @@ var Fitted = []Size{
|
|||
|
||||
// Fit returns the largest fitting thumbnail size.
|
||||
func Fit(w, h int) (size Size) {
|
||||
j := len(Fitted) - 1
|
||||
j := len(FitSizes) - 1
|
||||
|
||||
for i := j; i >= 0; i-- {
|
||||
if size = Fitted[i]; w <= size.Width && h <= size.Height {
|
||||
if size = FitSizes[i]; w <= size.Width && h <= size.Height {
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
return Fitted[0]
|
||||
return FitSizes[0]
|
||||
}
|
||||
|
||||
// FitBounds returns the largest thumbnail size fitting the rectangle.
|
||||
|
|
39
internal/thumb/report.go
Normal file
39
internal/thumb/report.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package thumb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/report"
|
||||
)
|
||||
|
||||
// Report returns a file format documentation table.
|
||||
func Report(sizes SizeList, short bool) (rows [][]string, cols []string) {
|
||||
if short {
|
||||
cols = []string{"Size", "Usage"}
|
||||
} else {
|
||||
cols = []string{"Name", "Width", "Height", "Aspect Ratio", "Usage"}
|
||||
}
|
||||
|
||||
sorted := append(SizeList{}, sizes...)
|
||||
|
||||
sort.Slice(sorted, func(i, j int) bool {
|
||||
if sorted[i].Width == sorted[j].Width {
|
||||
return sorted[i].Name < sorted[j].Name
|
||||
} else {
|
||||
return sorted[i].Width < sorted[j].Width
|
||||
}
|
||||
})
|
||||
|
||||
rows = make([][]string, 0, len(sorted))
|
||||
|
||||
for _, s := range sorted {
|
||||
if short {
|
||||
rows = append(rows, []string{fmt.Sprintf("%d", s.Width), s.Usage})
|
||||
} else {
|
||||
rows = append(rows, []string{s.Name.String(), fmt.Sprintf("%d", s.Width), fmt.Sprintf("%d", s.Height), report.Bool(s.Fit, "Preserved", "1:1"), s.Usage})
|
||||
}
|
||||
}
|
||||
|
||||
return rows, cols
|
||||
}
|
20
internal/thumb/report_test.go
Normal file
20
internal/thumb/report_test.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package thumb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestReport(t *testing.T) {
|
||||
t.Run("Videos", func(t *testing.T) {
|
||||
rows, cols := Report(VideoSizes, true)
|
||||
assert.Equal(t, 2, len(cols))
|
||||
assert.Equal(t, len(VideoSizes), len(rows))
|
||||
})
|
||||
t.Run("Thumbs", func(t *testing.T) {
|
||||
rows, cols := Report(Sizes.All(), false)
|
||||
assert.Equal(t, 5, len(cols))
|
||||
assert.Equal(t, len(Sizes), len(rows))
|
||||
})
|
||||
}
|
|
@ -4,10 +4,11 @@ import (
|
|||
"image"
|
||||
)
|
||||
|
||||
// Size represents a standard media resolution.
|
||||
type Size struct {
|
||||
Name Name `json:"name"`
|
||||
Source Name `json:"-"`
|
||||
Use string `json:"use"`
|
||||
Usage string `json:"usage"`
|
||||
Width int `json:"w"`
|
||||
Height int `json:"h"`
|
||||
Public bool `json:"-"`
|
||||
|
|
|
@ -6,7 +6,7 @@ var (
|
|||
Filter = ResampleLanczos
|
||||
)
|
||||
|
||||
// MaxSize returns the max supported thumb size in pixels.
|
||||
// MaxSize returns the max supported size in pixels.
|
||||
func MaxSize() int {
|
||||
if SizePrecached > SizeUncached {
|
||||
return SizePrecached
|
||||
|
@ -15,29 +15,43 @@ func MaxSize() int {
|
|||
return SizeUncached
|
||||
}
|
||||
|
||||
// InvalidSize tests if the thumb size in pixels is invalid.
|
||||
// InvalidSize tests if the size in pixels is invalid.
|
||||
func InvalidSize(size int) bool {
|
||||
return size < 0 || size > MaxSize()
|
||||
}
|
||||
|
||||
// SizeList represents a list of sizes.
|
||||
type SizeList []Size
|
||||
|
||||
// SizeMap maps size names to sizes.
|
||||
type SizeMap map[Name]Size
|
||||
|
||||
// All returns a slice containing all sizes.
|
||||
func (m SizeMap) All() SizeList {
|
||||
result := make(SizeList, 0, len(m))
|
||||
|
||||
for _, s := range m {
|
||||
result = append(result, s)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Sizes contains the properties of all thumbnail sizes.
|
||||
var Sizes = SizeMap{
|
||||
Tile50: {Tile50, Tile500, "Lists", 50, 50, false, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||
Tile100: {Tile100, Tile500, "Maps", 100, 100, false, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||
Tile224: {Tile224, Tile500, "TensorFlow, Mosaic", 224, 224, false, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||
Tile500: {Tile500, "", "Tiles", 500, 500, false, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||
Tile50: {Tile50, Tile500, "List View", 50, 50, false, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||
Tile100: {Tile100, Tile500, "Places View", 100, 100, false, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||
Tile224: {Tile224, Tile500, "TensorFlow, Mosaic View", 224, 224, false, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||
Tile500: {Tile500, "", "Cards View", 500, 500, false, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||
Colors: {Colors, Fit720, "Color Detection", 3, 3, false, false, []ResampleOption{ResampleResize, ResampleNearestNeighbor, ResamplePng}},
|
||||
Left224: {Left224, Fit720, "TensorFlow", 224, 224, false, false, []ResampleOption{ResampleFillTopLeft, ResampleDefault}},
|
||||
Right224: {Right224, Fit720, "TensorFlow", 224, 224, false, false, []ResampleOption{ResampleFillBottomRight, ResampleDefault}},
|
||||
Fit720: {Fit720, "", "Mobile, TV", 720, 720, true, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit1280: {Fit1280, Fit2048, "Mobile, HD Ready TV", 1280, 1024, true, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit1920: {Fit1920, Fit2048, "Mobile, Full HD TV", 1920, 1200, true, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit2048: {Fit2048, "", "Tablets, Cinema 2K", 2048, 2048, true, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit2560: {Fit2560, "", "Quad HD, Retina Display", 2560, 1600, true, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit3840: {Fit3840, "", "Ultra HD", 3840, 2400, true, true, []ResampleOption{ResampleFit, ResampleDefault}}, // Deprecated in favor of fit_4096
|
||||
Fit4096: {Fit4096, "", "Ultra HD, Retina 4K", 4096, 4096, true, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit7680: {Fit7680, "", "8K Ultra HD 2, Retina 6K", 7680, 4320, true, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit720: {Fit720, "", "SD TV, Mobile", 720, 720, true, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit1280: {Fit1280, Fit2048, "HD TV, SXGA", 1280, 1024, true, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit1920: {Fit1920, Fit2048, "Full HD", 1920, 1200, true, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit2048: {Fit2048, "", "DCI 2K, Tablets", 2048, 2048, true, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit2560: {Fit2560, "", "Quad HD, Notebooks", 2560, 1600, true, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit3840: {Fit3840, "", "4K Ultra HD", 3840, 2400, true, true, []ResampleOption{ResampleFit, ResampleDefault}}, // Deprecated in favor of fit_4096
|
||||
Fit4096: {Fit4096, "", "DCI 4K, Retina 4K", 4096, 4096, true, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit7680: {Fit7680, "", "8K Ultra HD 2", 7680, 4320, true, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
}
|
||||
|
|
37
internal/thumb/video.go
Normal file
37
internal/thumb/video.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package thumb
|
||||
|
||||
// VideoSizes contains all valid video output sizes sorted by size.
|
||||
var VideoSizes = SizeList{
|
||||
Sizes[Fit7680],
|
||||
Sizes[Fit4096],
|
||||
Sizes[Fit3840],
|
||||
Sizes[Fit2560],
|
||||
Sizes[Fit2048],
|
||||
Sizes[Fit1920],
|
||||
Sizes[Fit1280],
|
||||
Sizes[Fit720],
|
||||
}
|
||||
|
||||
// VideoSize returns the largest video size type for the given width limit.
|
||||
func VideoSize(limit int) Size {
|
||||
if limit < 0 {
|
||||
// Return maximum size.
|
||||
return Sizes[Fit7680]
|
||||
} else if limit == 0 {
|
||||
// Return default size.
|
||||
return Sizes[Fit3840]
|
||||
} else if limit <= 720 {
|
||||
// Return minimum size.
|
||||
return Sizes[Fit720]
|
||||
}
|
||||
|
||||
// Find match.
|
||||
for _, t := range VideoSizes {
|
||||
if t.Width <= limit {
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
||||
// Return maximum size.
|
||||
return Sizes[Fit7680]
|
||||
}
|
22
internal/thumb/video_test.go
Normal file
22
internal/thumb/video_test.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package thumb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestVideoSize(t *testing.T) {
|
||||
assert.Equal(t, Sizes[Fit720], VideoSize(720))
|
||||
assert.Equal(t, Sizes[Fit720], VideoSize(1279))
|
||||
assert.Equal(t, Sizes[Fit1280], VideoSize(1280))
|
||||
assert.Equal(t, Sizes[Fit1280], VideoSize(1281))
|
||||
assert.Equal(t, Sizes[Fit1920], VideoSize(1920))
|
||||
assert.Equal(t, Sizes[Fit1920], VideoSize(2000))
|
||||
assert.Equal(t, Sizes[Fit2048], VideoSize(2048))
|
||||
assert.Equal(t, Sizes[Fit2560], VideoSize(3000))
|
||||
assert.Equal(t, Sizes[Fit3840], VideoSize(0))
|
||||
assert.Equal(t, Sizes[Fit3840], VideoSize(4000))
|
||||
assert.Equal(t, Sizes[Fit7680], VideoSize(8000))
|
||||
assert.Equal(t, Sizes[Fit7680], VideoSize(-1))
|
||||
}
|
Loading…
Reference in a new issue