Videos: Stream OGV, VP8, VP9, AV1, WebM, and HEVC if supported #2461

This commit is contained in:
Michael Mayer 2022-06-24 06:59:22 +02:00
parent 95c03afe28
commit 519f0c49c9
24 changed files with 547 additions and 44 deletions

BIN
assets/static/video/404.mp4 Normal file

Binary file not shown.

View file

@ -0,0 +1,47 @@
/*
Copyright (c) 2018 - 2022 PhotoPrism UG. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under Version 3 of the GNU Affero General Public License (the "AGPL"):
<https://docs.photoprism.app/license/agpl>
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
The AGPL is supplemented by our Trademark and Brand Guidelines,
which describe how our Brand Assets may be used:
<https://photoprism.app/trademark>
Feel free to send an email to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
<https://docs.photoprism.app/developer-guide/>
*/
export const canUseVideo = !!document.createElement("video").canPlayType;
export const canUseAvc = canUseVideo
? !!document.createElement("video").canPlayType('video/mp4; codecs="avc1"')
: false;
export const canUseOGV = canUseVideo // Ogg Theora
? !!document.createElement("video").canPlayType("video/ogg")
: false;
export const canUseVP8 = canUseVideo // WebM VP8
? !!document.createElement("video").canPlayType('video/webm; codecs="vp8"')
: false;
export const canUseVP9 = canUseVideo // WebM VP9
? !!document.createElement("video").canPlayType('video/webm; codecs="vp9"')
: false;
export const canUseAv1 = canUseVideo // AV1, Main Profile, Level 4.0 Main Tier, 8-bit
? !!document.createElement("video").canPlayType('video/webm; codecs="av01.0.08M.08"')
: false;
export const canUseWebm = canUseVideo
? !!document.createElement("video").canPlayType("video/webm")
: false;
export const canUseHevc = canUseVideo
? !!document.createElement("video").canPlayType('video/mp4; codecs="hvc1"')
: false;

View file

@ -37,12 +37,19 @@ import { $gettext } from "common/vm";
import Clipboard from "common/clipboard";
import download from "common/download";
import * as src from "common/src";
import { canUseOGV, canUseVP8, canUseVP9, canUseAv1, canUseWebm, canUseHevc } from "common/caniuse";
export const CodecOGV = "ogv";
export const CodecVP8 = "vp8";
export const CodecVP9 = "vp9";
export const CodecAv1 = "av01";
export const CodecAvc1 = "avc1";
export const CodecHvc1 = "hvc1";
export const FormatMp4 = "mp4";
export const FormatAv1 = "av01";
export const FormatAvc = "avc";
export const FormatHvc = "hvc";
export const FormatHevc = "hevc";
export const FormatWebM = "webm";
export const FormatGif = "gif";
export const FormatJpeg = "jpg";
export const MediaImage = "image";
@ -476,14 +483,25 @@ export class Photo extends RestModel {
videoUrl() {
let file = this.videoFile();
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
if (file && file.Codec === CodecHvc1 && isSafari) {
return `${config.apiUri}/videos/${file.Hash}/${config.previewToken()}/${FormatHvc}`;
}
if (file) {
return `${config.apiUri}/videos/${file.Hash}/${config.previewToken()}/${FormatAvc}`;
let videoFormat = FormatAvc;
if (canUseHevc && file.Codec === CodecHvc1) {
videoFormat = FormatHevc;
} else if (canUseOGV && file.Codec === CodecOGV) {
videoFormat = CodecOGV;
} else if (canUseVP8 && file.Codec === CodecVP8) {
videoFormat = CodecVP8;
} else if (canUseVP9 && file.Codec === CodecVP9) {
videoFormat = CodecVP9;
} else if (canUseAv1 && file.Codec === CodecAv1) {
videoFormat = FormatAv1;
} else if (canUseWebm && file.FileType === FormatWebM) {
videoFormat = FormatWebM;
}
return `${config.apiUri}/videos/${file.Hash}/${config.previewToken()}/${videoFormat}`;
}
return `${config.apiUri}/videos/${this.Hash}/${config.previewToken()}/${FormatAvc}`;

View file

@ -0,0 +1,48 @@
import "../fixtures";
import {
canUseAv1,
canUseAvc,
canUseHevc,
canUseOGV,
canUseVideo,
canUseVP8,
canUseVP9,
canUseWebm,
} from "common/caniuse";
let chai = require("chai/chai");
let assert = chai.assert;
describe("common/caniuse", () => {
it("canUseVideo", () => {
assert.equal(canUseVideo, true);
});
it("canUseAvc", () => {
assert.equal(canUseAvc, true);
});
it("canUseOGV", () => {
assert.equal(canUseOGV, true);
});
it("canUseVP8", () => {
assert.equal(canUseVP8, true);
});
it("canUseVP9", () => {
assert.equal(canUseVP9, true);
});
it("canUseAv1", () => {
assert.equal(canUseAv1, true);
});
it("canUseWebm", () => {
assert.equal(canUseWebm, true);
});
it("canUseHevc", () => {
assert.equal(canUseHevc, false);
});
});

View file

@ -11,7 +11,6 @@ import (
const (
ContentTypeAvc = `video/mp4; codecs="avc1"`
ContentTypeHvc = `video/mp4; codecs="hvc1"`
)
// AddCacheHeader adds a cache control header to the response.

View file

@ -1,6 +1,7 @@
package api
import (
"fmt"
"net/http"
"github.com/photoprism/photoprism/pkg/video"
@ -65,28 +66,32 @@ func GetVideo(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.FileName)
if mf, err := photoprism.NewMediaFile(fileName); err != nil {
log.Errorf("video: file %s is missing", clean.Log(f.FileName))
c.Data(http.StatusOK, "image/svg+xml", videoIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore.
logError("video", f.Update("FileMissing", true))
return
} else if f.FileCodec != string(format.Codec) {
// Log error and default to 404.mp4
log.Errorf("video: file %s is missing", clean.Log(f.FileName))
fileName = service.Config().StaticFile("video/404.mp4")
AddContentTypeHeader(c, ContentTypeAvc)
} else if f.FileCodec != "" && f.FileCodec == string(format.Codec) || format.Codec == video.UnknownCodec && f.FileType == string(format.File) {
if f.FileCodec != "" && f.FileCodec != f.FileType {
log.Debugf("video: %s has matching codec %s", clean.Log(f.FileName), clean.Log(f.FileCodec))
AddContentTypeHeader(c, fmt.Sprintf("%s; codecs=\"%s\"", f.FileMime, clean.Codec(f.FileCodec)))
} else {
log.Debugf("video: %s has matching type %s", clean.Log(f.FileName), clean.Log(f.FileType))
AddContentTypeHeader(c, f.FileMime)
}
} else {
conv := service.Convert()
if avcFile, err := conv.ToAvc(mf, service.Config().FFmpegEncoder(), false, false); err != nil {
// Log error and default to 404.mp4
log.Errorf("video: transcoding %s failed", clean.Log(f.FileName))
c.Data(http.StatusOK, "image/svg+xml", videoIconSvg)
return
fileName = service.Config().StaticFile("video/404.mp4")
} else {
fileName = avcFile.FileName()
}
}
if video.Types[formatName] == video.HEVC {
AddContentTypeHeader(c, ContentTypeHvc)
} else {
AddContentTypeHeader(c, ContentTypeAvc)
}

View file

@ -1,13 +1,20 @@
package api
import (
"fmt"
"net/http"
"testing"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/stretchr/testify/assert"
)
func TestGetVideo(t *testing.T) {
t.Run("ContentTypeAvc", func(t *testing.T) {
assert.Equal(t, ContentTypeAvc, fmt.Sprintf("%s; codecs=\"%s\"", "video/mp4", clean.Codec("avc1")))
})
t.Run("invalid hash", func(t *testing.T) {
app, router, conf := NewApiTest()
GetVideo(router)

View file

@ -69,11 +69,16 @@ func (c *Config) TemplateName() string {
return "index.tmpl"
}
// StaticPath returns the static assets path.
// StaticPath returns the static assets' path.
func (c *Config) StaticPath() string {
return filepath.Join(c.AssetsPath(), "static")
}
// StaticFile returns the path to a static file.
func (c *Config) StaticFile(fileName string) string {
return filepath.Join(c.AssetsPath(), "static", fileName)
}
// BuildPath returns the static build path.
func (c *Config) BuildPath() string {
return filepath.Join(c.StaticPath(), "build")

View file

@ -231,6 +231,13 @@ func TestConfig_StaticPath(t *testing.T) {
assert.Equal(t, "/go/src/github.com/photoprism/photoprism/assets/static", path)
}
func TestConfig_StaticFile(t *testing.T) {
c := NewConfig(CliTestContext())
path := c.StaticFile("video/404.mp4")
assert.Equal(t, "/go/src/github.com/photoprism/photoprism/assets/static/video/404.mp4", path)
}
func TestConfig_BuildPath(t *testing.T) {
c := NewConfig(CliTestContext())

View file

@ -5,6 +5,8 @@ import (
)
const CodecUnknown = ""
const CodecAv1 = string(video.CodecAV1)
const CodecVP9 = string(video.CodecVP9)
const CodecAvc1 = string(video.CodecAVC)
const CodecJpeg = "jpeg"
const CodecHeic = "heic"

View file

@ -25,7 +25,7 @@ type Data struct {
Duration time.Duration `meta:"Duration,MediaDuration,TrackDuration"`
FPS float64 `meta:"VideoFrameRate,VideoAvgFrameRate"`
Frames int `meta:"FrameCount"`
Codec string `meta:"CompressorID,FileType"`
Codec string `meta:"CompressorID,VideoCodecID,CodecID,FileType"`
Title string `meta:"Headline,Title" xmp:"dc:title" dc:"title,title.Alt"`
Subject string `meta:"Subject,PersonInImage,ObjectName,HierarchicalSubject,CatalogSets" xmp:"Subject"`
Keywords Keywords `meta:"Keywords"`

View file

@ -9,6 +9,8 @@ import (
"strings"
"time"
"github.com/photoprism/photoprism/pkg/video"
"github.com/photoprism/photoprism/pkg/projection"
"github.com/photoprism/photoprism/pkg/clean"
@ -292,10 +294,14 @@ func (data *Data) Exiftool(jsonData []byte, originalName string) (err error) {
}
}
// Normalize compression information.
// Normalize codec name.
data.Codec = strings.ToLower(data.Codec)
if strings.Contains(data.Codec, CodecJpeg) {
if strings.Contains(data.Codec, CodecJpeg) { // JPEG Image?
data.Codec = CodecJpeg
} else if c, ok := video.Codecs[data.Codec]; ok { // Video codec?
data.Codec = string(c)
} else if strings.HasPrefix(data.Codec, "a_") { // Audio codec?
data.Codec = ""
}
// Validate and normalize optional DocumentID.

View file

@ -4,11 +4,10 @@ import (
"testing"
"time"
"github.com/photoprism/photoprism/pkg/video"
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/pkg/projection"
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/pkg/video"
)
func TestJSON(t *testing.T) {
@ -40,6 +39,86 @@ func TestJSON(t *testing.T) {
assert.Equal(t, "", data.LensModel)
})
t.Run("yoga-av1.webm.json", func(t *testing.T) {
data, err := JSON("testdata/yoga-av1.webm.json", "")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "yoga-av1.webm", data.FileName)
assert.Equal(t, "", data.Codec)
assert.Equal(t, "20s", data.Duration.String())
assert.Equal(t, 854, data.Width)
assert.Equal(t, 480, data.Height)
assert.Equal(t, 854, data.ActualWidth())
assert.Equal(t, 480, data.ActualHeight())
})
t.Run("stream.webm.json", func(t *testing.T) {
data, err := JSON("testdata/stream.webm.json", "")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "stream.webm", data.FileName)
assert.Equal(t, CodecAv1, data.Codec)
assert.Equal(t, "2m24s", data.Duration.String())
assert.Equal(t, 1280, data.Width)
assert.Equal(t, 720, data.Height)
assert.Equal(t, 1280, data.ActualWidth())
assert.Equal(t, 720, data.ActualHeight())
})
t.Run("earth.ogv.json", func(t *testing.T) {
data, err := JSON("testdata/earth.ogv.json", "")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "earth.ogv", data.FileName)
assert.Equal(t, string(video.CodecOGV), data.Codec)
assert.Equal(t, "0s", data.Duration.String())
assert.Equal(t, 1280, data.Width)
assert.Equal(t, 720, data.Height)
assert.Equal(t, 1280, data.ActualWidth())
assert.Equal(t, 720, data.ActualHeight())
})
t.Run("webm-vp8.json", func(t *testing.T) {
data, err := JSON("testdata/webm-vp8.json", "")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "earth.vp8.webm", data.FileName)
assert.Equal(t, string(video.CodecVP8), data.Codec)
assert.Equal(t, "30s", data.Duration.String())
assert.Equal(t, 1920, data.Width)
assert.Equal(t, 1080, data.Height)
assert.Equal(t, 1920, data.ActualWidth())
assert.Equal(t, 1080, data.ActualHeight())
})
t.Run("webm-vp9.json", func(t *testing.T) {
data, err := JSON("testdata/webm-vp9.json", "")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "earth-animation.ogv.720p.vp9.webm", data.FileName)
assert.Equal(t, string(video.CodecVP9), data.Codec)
assert.Equal(t, "8s", data.Duration.String())
assert.Equal(t, 1280, data.Width)
assert.Equal(t, 720, data.Height)
assert.Equal(t, 1280, data.ActualWidth())
assert.Equal(t, 720, data.ActualHeight())
})
t.Run("gopher-telegram.json", func(t *testing.T) {
data, err := JSON("testdata/gopher-telegram.json", "")

28
internal/meta/testdata/earth.ogv.json vendored Normal file
View file

@ -0,0 +1,28 @@
[{
"SourceFile": "earth.ogv",
"ExifToolVersion": 12.42,
"FileName": "earth.ogv",
"Directory": ".",
"FileSize": 6397571,
"FileModifyDate": "2022:06:24 03:16:55+00:00",
"FileAccessDate": "2022:06:24 03:18:39+00:00",
"FileInodeChangeDate": "2022:06:24 03:18:39+00:00",
"FilePermissions": 100664,
"FileType": "OGV",
"FileTypeExtension": "OGV",
"MIMEType": "video/ogg",
"TheoraVersion": "3 2 0",
"ImageWidth": 1280,
"ImageHeight": 720,
"XOffset": 0,
"YOffset": 0,
"FrameRate": 30,
"ColorSpace": 1,
"NominalVideoBitrate": 0,
"Quality": 63,
"PixelFormat": 0,
"Vendor": "Xiph.Org libTheora I 20060526 3 2 0",
"Encoder": "ffmpeg2theora 0.19",
"ImageSize": "1280 720",
"Megapixels": 0.9216
}]

30
internal/meta/testdata/stream.webm.json vendored Normal file
View file

@ -0,0 +1,30 @@
[{
"SourceFile": "stream.webm",
"ExifToolVersion": 12.42,
"FileName": "stream.webm",
"Directory": ".",
"FileSize": 57052418,
"FileModifyDate": "2022:06:24 01:43:22+00:00",
"FileAccessDate": "2022:06:24 01:43:39+00:00",
"FileInodeChangeDate": "2022:06:24 01:44:40+00:00",
"FilePermissions": 100664,
"FileType": "WEBM",
"FileTypeExtension": "WEBM",
"MIMEType": "video/webm",
"EBMLVersion": 1,
"EBMLReadVersion": 1,
"DocType": "webm",
"DocTypeVersion": 4,
"DocTypeReadVersion": 2,
"TimecodeScale": 0.001,
"Duration": 144.12,
"MuxingApp": "libwebm-0.2.1.0",
"WritingApp": "aomenc 1.0.0",
"TrackNumber": 1,
"TrackType": 1,
"VideoCodecID": "V_AV1",
"ImageWidth": 1280,
"ImageHeight": 720,
"ImageSize": "1280 720",
"Megapixels": 0.9216
}]

32
internal/meta/testdata/webm-vp8.json vendored Normal file
View file

@ -0,0 +1,32 @@
[{
"SourceFile": "earth.vp8.webm",
"ExifToolVersion": 12.42,
"FileName": "earth.vp8.webm",
"Directory": ".",
"FileSize": 2962571,
"FileModifyDate": "2022:06:24 03:17:47+00:00",
"FileAccessDate": "2022:06:24 03:18:39+00:00",
"FileInodeChangeDate": "2022:06:24 03:18:39+00:00",
"FilePermissions": 100664,
"FileType": "WEBM",
"FileTypeExtension": "WEBM",
"MIMEType": "video/webm",
"EBMLVersion": 1,
"EBMLReadVersion": 1,
"DocType": "webm",
"DocTypeVersion": 2,
"DocTypeReadVersion": 2,
"TimecodeScale": 0.001,
"MuxingApp": "Lavf56.40.101",
"WritingApp": "Lavf56.40.101",
"Duration": 30,
"TrackNumber": 1,
"TrackLanguage": "eng",
"CodecID": "V_VP8",
"TrackType": 1,
"VideoFrameRate": 30.0000003,
"ImageWidth": 1920,
"ImageHeight": 1080,
"ImageSize": "1920 1080",
"Megapixels": 2.0736
}]

33
internal/meta/testdata/webm-vp9.json vendored Normal file
View file

@ -0,0 +1,33 @@
[{
"SourceFile": "earth-animation.ogv.720p.vp9.webm",
"ExifToolVersion": 12.42,
"FileName": "earth-animation.ogv.720p.vp9.webm",
"Directory": ".",
"FileSize": 1806960,
"FileModifyDate": "2022:06:24 02:37:55+00:00",
"FileAccessDate": "2022:06:24 02:40:20+00:00",
"FileInodeChangeDate": "2022:06:24 02:40:20+00:00",
"FilePermissions": 100664,
"FileType": "WEBM",
"FileTypeExtension": "WEBM",
"MIMEType": "video/webm",
"EBMLVersion": 1,
"EBMLReadVersion": 1,
"DocType": "webm",
"DocTypeVersion": 2,
"DocTypeReadVersion": 2,
"TimecodeScale": 0.001,
"MuxingApp": "Lavf57.56.101",
"WritingApp": "Lavf57.56.101",
"Duration": 8.033,
"TrackNumber": 1,
"TrackLanguage": "und",
"CodecID": "V_VP9",
"TrackType": 1,
"VideoFrameRate": 30.0000003,
"ImageWidth": 1280,
"ImageHeight": 720,
"VideoScanType": 2,
"ImageSize": "1280 720",
"Megapixels": 0.9216
}]

View file

@ -0,0 +1,37 @@
[{
"SourceFile": "yoga-av1.webm",
"ExifToolVersion": 12.42,
"FileName": "yoga-av1.webm",
"Directory": ".",
"FileSize": 1826192,
"FileModifyDate": "2022:06:24 01:25:23+00:00",
"FileAccessDate": "2022:06:24 01:26:04+00:00",
"FileInodeChangeDate": "2022:06:24 01:26:04+00:00",
"FilePermissions": 100664,
"FileType": "WEBM",
"FileTypeExtension": "WEBM",
"MIMEType": "video/webm",
"EBMLVersion": 1,
"EBMLReadVersion": 1,
"DocType": "webm",
"DocTypeVersion": 4,
"DocTypeReadVersion": 2,
"TimecodeScale": 0.001,
"MuxingApp": "Lavf58.37.100",
"WritingApp": "Lavf58.37.100",
"Duration": 20.302,
"VideoFrameRate": 25,
"ImageWidth": 854,
"ImageHeight": 480,
"TrackNumber": 2,
"TrackLanguage": "eng",
"CodecID": "A_OPUS",
"TrackType": 2,
"AudioChannels": 2,
"AudioSampleRate": 48000,
"AudioBitsPerSample": 32,
"TagName": "DURATION",
"TagString": "00:00:20.302000000",
"ImageSize": "854 480",
"Megapixels": 0.40992
}]

23
pkg/clean/codec.go Normal file
View file

@ -0,0 +1,23 @@
package clean
import (
"strings"
)
// Codec removes non-alphanumeric characters from a string and returns it.
func Codec(s string) string {
if s == "" {
return ""
}
// Remove unwanted characters.
s = strings.Map(func(r rune) rune {
if (r < '0' || r > '9') && (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') && r != '_' {
return -1
}
return r
}, s)
return s
}

28
pkg/clean/codec_test.go Normal file
View file

@ -0,0 +1,28 @@
package clean
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCodec(t *testing.T) {
t.Run("UUID", func(t *testing.T) {
assert.Equal(t, "123e4567e89b12d3A456426614174000", Codec("123e4567-e89b-12d3-A456-426614174000 "))
})
t.Run("left_224", func(t *testing.T) {
assert.Equal(t, "left_224", Codec("left_224"))
})
t.Run("VP09", func(t *testing.T) {
assert.Equal(t, "VP09", Codec("VP09"))
})
t.Run("v_vp9", func(t *testing.T) {
assert.Equal(t, "v_vp9", Codec("v_vp9"))
})
t.Run("SHA1", func(t *testing.T) {
assert.Equal(t, "5c50ae14f339364eb8224f23c2d3abc7e79016f3READMEmd", Codec("5c50ae14f339364eb8224f23c2d3abc7e79016f3 README.md"))
})
t.Run("Quotes", func(t *testing.T) {
assert.Equal(t, "fooBaaar23", Codec("\"foo\" Baa'ar 2```3"))
})
}

View file

@ -16,6 +16,9 @@ func TestIdString(t *testing.T) {
t.Run("SHA1", func(t *testing.T) {
assert.Equal(t, "5c50ae14f339364eb8224f23c2d3abc7e79016f3readmemd", IdString("5c50ae14f339364eb8224f23c2d3abc7e79016f3 README.md"))
})
t.Run("Quotes", func(t *testing.T) {
assert.Equal(t, "foobaaar23", IdString("\"foo\" baa'ar 2```3"))
})
}
func TestIdUint(t *testing.T) {

View file

@ -2,25 +2,48 @@ package video
type Codec string
// Check browser support: https://cconcolato.github.io/media-mime-support/
const (
UnknownCodec Codec = ""
CodecAVC Codec = "avc1"
CodecHEVC Codec = "hvc1"
CodecVVC Codec = "vvc"
CodecAV1 Codec = "av01"
CodecVP8 Codec = "vp8"
CodecVP9 Codec = "vp9"
CodecOGV Codec = "ogv"
CodecWebM Codec = "webm"
)
// Codecs maps identifiers to codecs.
var Codecs = StandardCodecs{
"": UnknownCodec,
"avc": CodecAVC,
"avc1": CodecAVC,
"hvc1": CodecHEVC,
"hvc": CodecHEVC,
"hevc": CodecHEVC,
"vvc": CodecVVC,
"av1": CodecAV1,
"av01": CodecAV1,
"": UnknownCodec,
"a_opus": UnknownCodec,
"a_vorbis": UnknownCodec,
"avc": CodecAVC,
"avc1": CodecAVC,
"v_avc": CodecAVC,
"v_avc1": CodecAVC,
"hevc": CodecHEVC,
"hvc": CodecHEVC,
"hvc1": CodecHEVC,
"v_hvc": CodecHEVC,
"v_hvc1": CodecHEVC,
"vvc": CodecVVC,
"v_vvc": CodecVVC,
"av1": CodecAV1,
"av01": CodecAV1,
"v_av1": CodecAV1,
"v_av01": CodecAV1,
"vp8": CodecVP8,
"vp80": CodecVP8,
"v_vp8": CodecVP8,
"vp9": CodecVP9,
"vp90": CodecVP9,
"v_vp9": CodecVP9,
"ogv": CodecOGV,
"webm": CodecWebM,
}
// StandardCodecs maps names to known codecs.

View file

@ -12,8 +12,15 @@ var Types = Standards{
"hevc": HEVC,
"vvc": VVC,
"vvc1": VVC,
"vp8": VP8,
"vp80": VP8,
"vp9": VP9,
"vp90": VP9,
"av1": AV1,
"av01": AV1,
"ogg": OGV,
"ogv": OGV,
"webm": WebM,
}
// Standards maps names to standardized formats.

View file

@ -22,15 +22,6 @@ var AVC = Type{
Public: true,
}
// AV1 aka AOMedia Video 1.
var AV1 = Type{
File: fs.VideoAV1,
Codec: CodecAV1,
Width: 0,
Height: 0,
Public: false,
}
// HEVC aka High Efficiency Video Coding (H.265).
var HEVC = Type{
File: fs.VideoHEVC,
@ -48,3 +39,48 @@ var VVC = Type{
Height: 0,
Public: false,
}
// VP8 + Google WebM.
var VP8 = Type{
File: fs.VideoWebM,
Codec: CodecVP8,
Width: 0,
Height: 0,
Public: false,
}
// VP9 + Google WebM.
var VP9 = Type{
File: fs.VideoWebM,
Codec: CodecVP9,
Width: 0,
Height: 0,
Public: false,
}
// AV1 + Google WebM.
var AV1 = Type{
File: fs.VideoWebM,
Codec: CodecAV1,
Width: 0,
Height: 0,
Public: false,
}
// OGV aka Ogg/Theora.
var OGV = Type{
File: fs.VideoOGV,
Codec: CodecOGV,
Width: 0,
Height: 0,
Public: false,
}
// WebM Container.
var WebM = Type{
File: fs.VideoWebM,
Codec: UnknownCodec,
Width: 0,
Height: 0,
Public: false,
}