Videos: Enforce transcoding to MPEG-4 AVC #603

This commit is contained in:
Michael Mayer 2020-12-12 17:20:31 +01:00
parent f3d60ae095
commit 473566f404
30 changed files with 342 additions and 295 deletions

View file

@ -52,7 +52,7 @@
flat icon large absolute class="p-photo-select">
<v-icon color="white" class="t-select t-on">check_circle</v-icon>
</v-btn>
<v-btn v-else-if="!selection.length && props.item.Type === 'video' && props.item.isPlayable()"
<v-btn v-else-if="!selection.length && (props.item.Type === 'video' || props.item.Type === 'live')"
:ripple="false"
flat icon large absolute class="p-photo-play opacity-75"
@click.stop.prevent="openPhoto(props.index, true)">

View file

@ -39,9 +39,13 @@ import {$gettext} from "common/vm";
export const SrcManual = "manual";
export const CodecAvc1 = "avc1";
export const TypeMP4 = "mp4";
export const TypeJpeg = "jpg";
export const FormatMp4 = "mp4";
export const FormatAvc = "avc";
export const FormatJpeg = "jpg";
export const TypeImage = "image";
export const TypeVideo= "video";
export const TypeLive = "live";
export const TypeRaw = "raw";
export const YearUnknown = -1;
export const MonthUnknown = -1;
export const DayUnknown = -1;
@ -260,7 +264,7 @@ export class Photo extends RestModel {
let file = this.Files.find(f => f.Codec === CodecAvc1);
if (!file) {
file = this.Files.find(f => f.Type === TypeMP4);
file = this.Files.find(f => f.Type === FormatMp4);
}
if (!file) {
@ -274,10 +278,10 @@ export class Photo extends RestModel {
const file = this.videoFile();
if (file) {
return `/api/v1/videos/${file.Hash}/${config.previewToken()}/${TypeMP4}`;
return `/api/v1/videos/${file.Hash}/${config.previewToken()}/${FormatAvc}`;
}
return `/api/v1/videos/${this.Hash}/${config.previewToken()}/${TypeMP4}`;
return `/api/v1/videos/${this.Hash}/${config.previewToken()}/${FormatAvc}`;
}
mainFile() {
@ -288,7 +292,7 @@ export class Photo extends RestModel {
let file = this.Files.find(f => !!f.Primary);
if (!file) {
file = this.Files.find(f => f.Type === TypeJpeg);
file = this.Files.find(f => f.Type === FormatJpeg);
}
return file;

View file

@ -2,6 +2,7 @@ import {$gettext} from "common/vm";
import moment from "moment-timezone";
import {Info} from "luxon";
import {config} from "../session";
import {TypeVideo,TypeImage,TypeLive,TypeRaw} from "../model/photo";
export const TimeZones = () => moment.tz.names();
@ -189,19 +190,19 @@ export const MapsStyle = () => [
export const PhotoTypes = () => [
{
"text": $gettext("Image"),
"value": "image",
"value": TypeImage,
},
{
"text": $gettext("Raw"),
"value": "raw",
"value": TypeRaw,
},
{
"text": $gettext("Live"),
"value": "live",
"value": TypeLive,
},
{
"text": $gettext("Video"),
"value": "video",
"value": TypeVideo,
},
];

View file

@ -46,7 +46,7 @@
</template>
<script>
import Photo from "model/photo";
import {Photo,TypeVideo,TypeLive} from "model/photo";
import Album from "model/album";
import Event from "pubsub-js";
import Thumb from "model/thumb";
@ -158,7 +158,7 @@ export default {
const selected = this.results[index];
if (showMerged && (selected.Type === 'video' || selected.Type === 'live')) {
if (showMerged && (selected.Type === TypeVideo || selected.Type === TypeLive)) {
if (this.results[index].isPlayable()) {
this.$modal.show('video', {video: selected, album: this.album});
} else {

View file

@ -24,7 +24,7 @@
</template>
<script>
import Photo from "model/photo";
import {Photo, TypeVideo, TypeLive} from "model/photo";
import mapboxgl from "mapbox-gl";
import Api from "common/api";
import Thumb from "model/thumb";
@ -193,7 +193,7 @@ export default {
const index = this.photos.findIndex((p) => p.UID === id);
const selected = this.photos[index];
if (selected.Type === 'video' || selected.Type === 'live') {
if (selected.Type === TypeVideo || selected.Type === TypeLive) {
this.$modal.show('video', {video: selected, album: null});
} else {
this.$viewer.show(Thumb.fromPhotos(this.photos), index)

View file

@ -48,7 +48,7 @@
flat icon large absolute class="p-photo-select">
<v-icon color="white" class="t-select t-on">check_circle</v-icon>
</v-btn>
<v-btn v-else-if="!selection.length && props.item.Type === 'video' && props.item.isPlayable()"
<v-btn v-else-if="!selection.length && (props.item.Type === 'video' || props.item.Type === 'live')"
:ripple="false"
flat icon large absolute class="p-photo-play opacity-75"
@click.stop.prevent="openPhoto(props.index, true)">
@ -174,7 +174,7 @@ export default {
} else if (this.photos[index]) {
let photo = this.photos[index];
if (photo.Type === 'video' && photo.isPlayable()) {
if (photo.Type === 'video' || photo.Type === 'live') {
this.openPhoto(index, true);
} else {
this.openPhoto(index, false);

View file

@ -86,7 +86,7 @@
</template>
<script>
import Photo from "model/photo";
import {Photo,TypeLive,TypeVideo} from "model/photo";
import Album from "model/album";
import Event from "pubsub-js";
import Thumb from "model/thumb";
@ -204,7 +204,7 @@ export default {
const selected = this.results[index];
if (showMerged && (selected.Type === 'video' || selected.Type === 'live')) {
if (showMerged && (selected.Type === TypeVideo || selected.Type === TypeLive)) {
if (this.results[index].isPlayable()) {
this.$modal.show('video', {video: selected, album: this.album});
} else {

View file

@ -390,18 +390,18 @@ describe("model/photo", () => {
});
it("should return video url", () => {
const values = {ID: 8, UID: "ABC123", Filename: "1980/01/superCuteKitten.jpg", FileUID: "123fgb", Files: [{UID: "123fgb", Name: "1980/01/superCuteKitten.jpg", Primary: true, Type: "jpg", Width: 500, Height: 600, Hash: "1xxbgdt53"}]};
const values = {ID: 8, UID: "ABC123", Filename: "1980/01/superCuteKitten.jpg", Hash: "703cf8f274fbb265d49c6262825780e1", FileUID: "123fgb", Files: [{UID: "123fgb", Name: "1980/01/superCuteKitten.jpg", Primary: true, Type: "jpg", Width: 500, Height: 600, Hash: "1xxbgdt53"}]};
const photo = new Photo(values);
assert.equal(photo.videoUrl(), "");
const values2 = {ID: 9, UID: "ABC163"};
assert.equal(photo.videoUrl(), "/api/v1/videos/703cf8f274fbb265d49c6262825780e1/static/avc");
const values2 = {ID: 9, UID: "ABC163", Hash: "2305e512e3b183ec982d60a8b608a8ca501973ba"};
const photo2 = new Photo(values2);
assert.equal(photo2.videoUrl(), false);
assert.equal(photo2.videoUrl(), "/api/v1/videos/2305e512e3b183ec982d60a8b608a8ca501973ba/static/avc");
const values3 = {ID: 10, UID: "ABC127", Filename: "1980/01/superCuteKitten.mp4", FileUID: "123fgb", Files: [{UID: "123fgb", Name: "1980/01/superCuteKitten.mp4", Primary: false, Type: "mp4", Width: 500, Height: 600, Hash: "1xxbgdt55"}]};
const photo3 = new Photo(values3);
assert.equal(photo3.videoUrl(), "/api/v1/videos/1xxbgdt55/static/mp4");
assert.equal(photo3.videoUrl(), "/api/v1/videos/1xxbgdt55/static/avc");
const values4 = {ID: 1, UID: "ABC128", Filename: "1980/01/superCuteKitten.jpg", FileUID: "123fgb", Files: [{UID: "123fgb", Name: "1980/01/superCuteKitten.jpg", Primary: false, Type: "jpg", Width: 500, Height: 600, Hash: "1xxbgdt53", Codec: "avc1"}]};
const photo4 = new Photo(values4);
assert.equal(photo4.videoUrl(), "/api/v1/videos/1xxbgdt53/static/mp4");
assert.equal(photo4.videoUrl(), "/api/v1/videos/1xxbgdt53/static/avc");
});
it("should return main file", () => {

View file

@ -2,6 +2,7 @@ package api
import (
"net/http"
"time"
"github.com/photoprism/photoprism/internal/service"
@ -27,7 +28,7 @@ func GetVideo(router *gin.RouterGroup) {
fileHash := c.Param("hash")
typeName := c.Param("type")
_, ok := video.Types[typeName]
videoType, ok := video.Types[typeName]
if !ok {
log.Errorf("video: invalid type %s", txt.Quote(typeName))
@ -69,18 +70,21 @@ func GetVideo(router *gin.RouterGroup) {
logError("video", f.Update("FileMissing", true))
return
} else if !mf.IsPlayableVideo() {
conv := service.Convert()
avcFile, err := conv.ToAvc1(mf)
} else if f.FileCodec != string(videoType.Codec) {
log.Debugf("video: transcoding %s from %s to avc", txt.Quote(f.FileName), txt.Quote(f.FileCodec))
if err != nil {
start := time.Now()
conv := service.Convert()
if avcFile, err := conv.ToAvc(mf); err != nil {
log.Errorf("video: failed transcoding %s", txt.Quote(f.FileName))
c.Data(http.StatusOK, "image/svg+xml", videoIconSvg)
return
} else {
fileName = avcFile.FileName()
}
fileName = avcFile.FileName()
log.Debugf("video: transcoding completed in %s", time.Since(start))
}
if c.Query("download") != "" {

View file

@ -305,7 +305,7 @@ func (m *File) RelatedPhoto() *Photo {
// NoJPEG returns true if the file is not a JPEG image file.
func (m *File) NoJPEG() bool {
return m.FileType != string(fs.TypeJpeg)
return m.FileType != string(fs.FormatJpeg)
}
// Links returns all share links for this entity.

View file

@ -6,7 +6,7 @@ const CodecAvc1 = "avc1"
const CodecHeic = "heic"
const CodecXMP = "xmp"
// CodecAvc1 returns true if the video is encoded with H.264/AVC
func (data Data) CodecAvc1() bool {
// CodecAvc returns true if the video format is MPEG-4 AVC.
func (data Data) CodecAvc() bool {
return data.Codec == CodecAvc1
}

View file

@ -12,7 +12,7 @@ func TestData_CodecAvc1(t *testing.T) {
Codec: "avc1",
}
assert.Equal(t, true, data.CodecAvc1())
assert.Equal(t, true, data.CodecAvc())
})
t.Run("false", func(t *testing.T) {
@ -20,6 +20,6 @@ func TestData_CodecAvc1(t *testing.T) {
Codec: "heic",
}
assert.Equal(t, false, data.CodecAvc1())
assert.Equal(t, false, data.CodecAvc())
})
}

View file

@ -38,14 +38,14 @@ func ValidDateTime(s string) bool {
}
// Exif parses an image file for Exif meta data and returns as Data struct.
func Exif(fileName string, fileType fs.FileType) (data Data, err error) {
func Exif(fileName string, fileType fs.FileFormat) (data Data, err error) {
err = data.Exif(fileName, fileType)
return data, err
}
// Exif parses an image file for Exif meta data and returns as Data struct.
func (data *Data) Exif(fileName string, fileType fs.FileType) (err error) {
func (data *Data) Exif(fileName string, fileType fs.FileFormat) (err error) {
exifMutex.Lock()
defer exifMutex.Unlock()

View file

@ -15,7 +15,7 @@ import (
"github.com/photoprism/photoprism/pkg/txt"
)
func RawExif(fileName string, fileType fs.FileType) (rawExif []byte, err error) {
func RawExif(fileName string, fileType fs.FileFormat) (rawExif []byte, err error) {
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("metadata: %s in %s (raw exif panic)\nstack: %s", e, txt.Quote(filepath.Base(fileName)), debug.Stack())
@ -27,7 +27,7 @@ func RawExif(fileName string, fileType fs.FileType) (rawExif []byte, err error)
logName := txt.Quote(filepath.Base(fileName))
if fileType == fs.TypeJpeg {
if fileType == fs.FormatJpeg {
jpegMp := jpegstructure.NewJpegMediaParser()
sl, err := jpegMp.ParseFile(fileName)
@ -49,7 +49,7 @@ func RawExif(fileName string, fileType fs.FileType) (rawExif []byte, err error)
parsed = true
}
}
} else if fileType == fs.TypePng {
} else if fileType == fs.FormatPng {
pngMp := pngstructure.NewPngMediaParser()
cs, err := pngMp.ParseFile(fileName)
@ -69,7 +69,7 @@ func RawExif(fileName string, fileType fs.FileType) (rawExif []byte, err error)
} else {
parsed = true
}
} else if fileType == fs.TypeHEIF {
} else if fileType == fs.FormatHEIF {
heicMp := heicexif.NewHeicExifMediaParser()
cs, err := heicMp.ParseFile(fileName)
@ -89,7 +89,7 @@ func RawExif(fileName string, fileType fs.FileType) (rawExif []byte, err error)
} else {
parsed = true
}
} else if fileType == fs.TypeTiff {
} else if fileType == fs.FormatTiff {
tiffMp := tiffstructure.NewTiffMediaParser()
cs, err := tiffMp.ParseFile(fileName)

View file

@ -9,7 +9,7 @@ import (
func TestExif(t *testing.T) {
t.Run("photoshop.jpg", func(t *testing.T) {
data, err := Exif("testdata/photoshop.jpg", fs.TypeJpeg)
data, err := Exif("testdata/photoshop.jpg", fs.FormatJpeg)
if err != nil {
t.Fatal(err)
@ -40,7 +40,7 @@ func TestExif(t *testing.T) {
})
t.Run("ladybug.jpg", func(t *testing.T) {
data, err := Exif("testdata/ladybug.jpg", fs.TypeJpeg)
data, err := Exif("testdata/ladybug.jpg", fs.FormatJpeg)
if err != nil {
t.Fatal(err)
@ -72,7 +72,7 @@ func TestExif(t *testing.T) {
})
t.Run("gopro_hd2.jpg", func(t *testing.T) {
data, err := Exif("testdata/gopro_hd2.jpg", fs.TypeJpeg)
data, err := Exif("testdata/gopro_hd2.jpg", fs.FormatJpeg)
if err != nil {
t.Fatal(err)
@ -101,7 +101,7 @@ func TestExif(t *testing.T) {
})
t.Run("tweethog.png", func(t *testing.T) {
_, err := Exif("testdata/tweethog.png", fs.TypePng)
_, err := Exif("testdata/tweethog.png", fs.FormatPng)
if err == nil {
t.Fatal("err should NOT be nil")
@ -111,7 +111,7 @@ func TestExif(t *testing.T) {
})
t.Run("iphone_7.heic", func(t *testing.T) {
data, err := Exif("testdata/iphone_7.heic", fs.TypeHEIF)
data, err := Exif("testdata/iphone_7.heic", fs.FormatHEIF)
if err != nil {
t.Fatal(err)
}
@ -132,7 +132,7 @@ func TestExif(t *testing.T) {
})
t.Run("gps-2000.jpg", func(t *testing.T) {
data, err := Exif("testdata/gps-2000.jpg", fs.TypeJpeg)
data, err := Exif("testdata/gps-2000.jpg", fs.FormatJpeg)
if err != nil {
t.Fatal(err)
@ -160,7 +160,7 @@ func TestExif(t *testing.T) {
})
t.Run("image-2011.jpg", func(t *testing.T) {
data, err := Exif("testdata/image-2011.jpg", fs.TypeJpeg)
data, err := Exif("testdata/image-2011.jpg", fs.FormatJpeg)
if err != nil {
t.Fatal(err)
@ -195,7 +195,7 @@ func TestExif(t *testing.T) {
})
t.Run("ship.jpg", func(t *testing.T) {
data, err := Exif("testdata/ship.jpg", fs.TypeJpeg)
data, err := Exif("testdata/ship.jpg", fs.FormatJpeg)
if err != nil {
t.Fatal(err)
@ -216,7 +216,7 @@ func TestExif(t *testing.T) {
})
t.Run("no-exif-data.jpg", func(t *testing.T) {
_, err := Exif("testdata/no-exif-data.jpg", fs.TypeJpeg)
_, err := Exif("testdata/no-exif-data.jpg", fs.FormatJpeg)
if err == nil {
t.Fatal("err should NOT be nil")
@ -226,7 +226,7 @@ func TestExif(t *testing.T) {
})
t.Run("screenshot.png", func(t *testing.T) {
data, err := Exif("testdata/screenshot.png", fs.TypePng)
data, err := Exif("testdata/screenshot.png", fs.FormatPng)
if err != nil {
t.Fatal(err)
@ -237,7 +237,7 @@ func TestExif(t *testing.T) {
})
t.Run("orientation.jpg", func(t *testing.T) {
data, err := Exif("testdata/orientation.jpg", fs.TypeJpeg)
data, err := Exif("testdata/orientation.jpg", fs.FormatJpeg)
if err != nil {
t.Fatal(err)
@ -265,13 +265,13 @@ func TestExif(t *testing.T) {
})
t.Run("gopher-preview.jpg", func(t *testing.T) {
_, err := Exif("testdata/gopher-preview.jpg", fs.TypeJpeg)
_, err := Exif("testdata/gopher-preview.jpg", fs.FormatJpeg)
assert.EqualError(t, err, "metadata: no exif header in gopher-preview.jpg (search and extract)")
})
t.Run("huawei-gps-error.jpg", func(t *testing.T) {
data, err := Exif("testdata/huawei-gps-error.jpg", fs.TypeJpeg)
data, err := Exif("testdata/huawei-gps-error.jpg", fs.FormatJpeg)
if err != nil {
t.Fatal(err)
@ -292,7 +292,7 @@ func TestExif(t *testing.T) {
})
t.Run("panorama360.jpg", func(t *testing.T) {
data, err := Exif("testdata/panorama360.jpg", fs.TypeJpeg)
data, err := Exif("testdata/panorama360.jpg", fs.FormatJpeg)
if err != nil {
t.Fatal(err)
@ -323,7 +323,7 @@ func TestExif(t *testing.T) {
})
t.Run("exif-example.tiff", func(t *testing.T) {
data, err := Exif("testdata/exif-example.tiff", fs.TypeTiff)
data, err := Exif("testdata/exif-example.tiff", fs.FormatTiff)
if err != nil {
t.Fatal(err)
@ -354,7 +354,7 @@ func TestExif(t *testing.T) {
})
t.Run("out-of-range-500.jpg", func(t *testing.T) {
data, err := Exif("testdata/out-of-range-500.jpg", fs.TypeJpeg)
data, err := Exif("testdata/out-of-range-500.jpg", fs.FormatJpeg)
if err != nil {
t.Fatal(err)
@ -385,7 +385,7 @@ func TestExif(t *testing.T) {
})
t.Run("digikam.jpg", func(t *testing.T) {
data, err := Exif("testdata/digikam.jpg", fs.TypeJpeg)
data, err := Exif("testdata/digikam.jpg", fs.FormatJpeg)
if err != nil {
t.Fatal(err)
@ -419,7 +419,7 @@ func TestExif(t *testing.T) {
})
t.Run("notebook.jpg", func(t *testing.T) {
data, err := Exif("testdata/notebook.jpg", fs.TypeJpeg)
data, err := Exif("testdata/notebook.jpg", fs.FormatJpeg)
if err != nil {
t.Fatal(err)

View file

@ -115,7 +115,7 @@ func (c *Convert) Start(path string) error {
// ToJson uses exiftool to export metadata to a json file.
func (c *Convert) ToJson(mf *MediaFile) (*MediaFile, error) {
jsonName := fs.TypeJson.FindFirst(mf.FileName(), []string{c.conf.SidecarPath(), fs.HiddenPath}, c.conf.OriginalsPath(), false)
jsonName := fs.FormatJson.FindFirst(mf.FileName(), []string{c.conf.SidecarPath(), fs.HiddenPath}, c.conf.OriginalsPath(), false)
result, err := NewMediaFile(jsonName)
@ -220,7 +220,7 @@ func (c *Convert) ToJpeg(image *MediaFile) (*MediaFile, error) {
return image, nil
}
jpegName := fs.TypeJpeg.FindFirst(image.FileName(), []string{c.conf.SidecarPath(), fs.HiddenPath}, c.conf.OriginalsPath(), false)
jpegName := fs.FormatJpeg.FindFirst(image.FileName(), []string{c.conf.SidecarPath(), fs.HiddenPath}, c.conf.OriginalsPath(), false)
mediaFile, err := NewMediaFile(jpegName)
@ -237,7 +237,7 @@ func (c *Convert) ToJpeg(image *MediaFile) (*MediaFile, error) {
log.Debugf("convert: %s -> %s", fileName, filepath.Base(jpegName))
xmpName := fs.TypeXMP.Find(image.FileName(), false)
xmpName := fs.FormatXMP.Find(image.FileName(), false)
event.Publish("index.converting", event.Data{
"fileType": image.FileType(),
@ -287,10 +287,18 @@ func (c *Convert) ToJpeg(image *MediaFile) (*MediaFile, error) {
return NewMediaFile(jpegName)
}
// AvcConvertCommand returns the command for converting video files to AVC1.
// AvcConvertCommand returns the command for converting video files to MPEG-4 AVC.
func (c *Convert) AvcConvertCommand(mf *MediaFile, avcName string) (result *exec.Cmd, useMutex bool, err error) {
if mf.IsVideo() {
result = exec.Command(c.conf.FFmpegBin(), "-i", mf.FileName(), avcName)
// Don't transcode more than one video at the same time.
useMutex = true
result = exec.Command(
c.conf.FFmpegBin(),
"-i", mf.FileName(),
"-c:v", "libx264",
"-f", "mp4",
avcName,
)
} else {
return nil, useMutex, fmt.Errorf("convert: file type %s not supported in %s", mf.FileType(), txt.Quote(mf.BaseName()))
}
@ -298,21 +306,17 @@ func (c *Convert) AvcConvertCommand(mf *MediaFile, avcName string) (result *exec
return result, useMutex, nil
}
// ToAvc1 converts a single video file to AVC1 if possible.
func (c *Convert) ToAvc1(video *MediaFile) (*MediaFile, error) {
// ToAvc converts a single video file to MPEG-4 AVC.
func (c *Convert) ToAvc(video *MediaFile) (*MediaFile, error) {
if !video.Exists() {
return nil, fmt.Errorf("convert: can not convert to avc1, file does not exist (%s)", video.RelName(c.conf.OriginalsPath()))
}
if video.IsPlayableVideo() {
return video, nil
}
avcName := fs.TypeMp4.FindFirst(video.FileName(), []string{c.conf.SidecarPath(), fs.HiddenPath}, c.conf.OriginalsPath(), false)
avcName := fs.FormatAvc.FindFirst(video.FileName(), []string{c.conf.SidecarPath(), fs.HiddenPath}, c.conf.OriginalsPath(), false)
mediaFile, err := NewMediaFile(avcName)
if err == nil && mediaFile.IsPlayableVideo() {
if err == nil && mediaFile.IsVideo() {
return mediaFile, nil
}
@ -335,6 +339,7 @@ func (c *Convert) ToAvc1(video *MediaFile) (*MediaFile, error) {
cmd, useMutex, err := c.AvcConvertCommand(video, avcName)
if err != nil {
log.Error(err)
return nil, err
}

View file

@ -232,7 +232,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
photo.PhotoQuality = -1
photo.PhotoSingle = o.Single
if yamlName := fs.TypeYaml.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), stripSequence); yamlName != "" {
if yamlName := fs.FormatYaml.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), stripSequence); yamlName != "" {
if err := photo.LoadFromYaml(yamlName); err != nil {
log.Errorf("index: %s in %s (restore from yaml)", err.Error(), logName)
} else if err := photo.Find(); err != nil {

View file

@ -31,7 +31,7 @@ type MediaFile struct {
statErr error
modTime time.Time
fileSize int64
fileType fs.FileType
fileType fs.FileFormat
mimeType string
takenAt time.Time
takenAtSrc string
@ -50,7 +50,7 @@ func NewMediaFile(fileName string) (*MediaFile, error) {
m := &MediaFile{
fileName: fileName,
fileRoot: entity.RootUnknown,
fileType: fs.TypeOther,
fileType: fs.FormatOther,
metaData: meta.NewData(),
width: -1,
height: -1,
@ -348,7 +348,7 @@ func (m *MediaFile) RelatedFiles(stripSequence bool) (result RelatedFiles, err e
// Add hidden JPEG if exists.
if !result.ContainsJpeg() {
if jpegName := fs.TypeJpeg.FindFirst(result.Main.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), stripSequence); jpegName != "" {
if jpegName := fs.FormatJpeg.FindFirst(result.Main.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), stripSequence); jpegName != "" {
if resultFile, err := NewMediaFile(jpegName); err == nil {
result.Files = append(result.Files, resultFile)
}
@ -654,7 +654,7 @@ func (m *MediaFile) IsGif() bool {
// IsTiff returns true if this is a TIFF file.
func (m *MediaFile) IsTiff() bool {
return m.HasFileType(fs.TypeTiff) && m.MimeType() == fs.MimeTypeTiff
return m.HasFileType(fs.FormatTiff) && m.MimeType() == fs.MimeTypeTiff
}
// IsHEIF returns true if this is a High Efficiency Image File Format file.
@ -674,24 +674,24 @@ func (m *MediaFile) IsVideo() bool {
// IsJson return true if this media file is a json sidecar file.
func (m *MediaFile) IsJson() bool {
return m.HasFileType(fs.TypeJson)
return m.HasFileType(fs.FormatJson)
}
// FileType returns the file type (jpg, gif, tiff,...).
func (m *MediaFile) FileType() fs.FileType {
func (m *MediaFile) FileType() fs.FileFormat {
switch {
case m.IsJpeg():
return fs.TypeJpeg
return fs.FormatJpeg
case m.IsPng():
return fs.TypePng
return fs.FormatPng
case m.IsGif():
return fs.TypeGif
return fs.FormatGif
case m.IsHEIF():
return fs.TypeHEIF
return fs.FormatHEIF
case m.IsBitmap():
return fs.TypeBitmap
return fs.FormatBitmap
default:
return fs.GetFileType(m.fileName)
return fs.GetFileFormat(m.fileName)
}
}
@ -701,8 +701,8 @@ func (m *MediaFile) MediaType() fs.MediaType {
}
// HasFileType returns true if this is the given type.
func (m *MediaFile) HasFileType(fileType fs.FileType) bool {
if fileType == fs.TypeJpeg {
func (m *MediaFile) HasFileType(fileType fs.FileFormat) bool {
if fileType == fs.FormatJpeg {
return m.IsJpeg()
}
@ -711,7 +711,7 @@ func (m *MediaFile) HasFileType(fileType fs.FileType) bool {
// IsRaw returns true if this is a RAW file.
func (m *MediaFile) IsRaw() bool {
return m.HasFileType(fs.TypeRaw)
return m.HasFileType(fs.FormatRaw)
}
// IsImageOther returns true if this is a PNG, GIF, BMP or TIFF file.
@ -726,7 +726,7 @@ func (m *MediaFile) IsImageOther() bool {
// IsXMP returns true if this is a XMP sidecar file.
func (m *MediaFile) IsXMP() bool {
return m.FileType() == fs.TypeXMP
return m.FileType() == fs.FormatXMP
}
// IsSidecar returns true if this is a sidecar file (containing metadata).
@ -736,7 +736,7 @@ func (m *MediaFile) IsSidecar() bool {
// IsPlayableVideo returns true if this is a supported video file format.
func (m *MediaFile) IsPlayableVideo() bool {
return m.IsVideo() && m.HasFileType(fs.TypeMp4)
return m.IsVideo() && (m.HasFileType(fs.FormatMp4) || m.HasFileType(fs.FormatAvc))
}
// IsPhoto returns true if this file is a photo / image.
@ -764,7 +764,7 @@ func (m *MediaFile) Jpeg() (*MediaFile, error) {
return m, nil
}
jpegFilename := fs.TypeJpeg.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false)
jpegFilename := fs.FormatJpeg.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false)
if jpegFilename == "" {
return nil, fmt.Errorf("no jpeg found for %s", m.FileName())
@ -784,7 +784,7 @@ func (m *MediaFile) HasJpeg() bool {
return true
}
jpegName := fs.TypeJpeg.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false)
jpegName := fs.FormatJpeg.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false)
if jpegName == "" {
m.hasJpeg = false
@ -801,7 +801,7 @@ func (m *MediaFile) HasJson() bool {
return true
}
return fs.TypeJson.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false) != ""
return fs.FormatJson.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false) != ""
}
func (m *MediaFile) decodeDimensions() error {

View file

@ -1151,7 +1151,7 @@ func TestMediaFile_IsPng(t *testing.T) {
t.Fatal(err)
}
assert.Equal(t, fs.TypePng, mediaFile.FileType())
assert.Equal(t, fs.FormatPng, mediaFile.FileType())
assert.Equal(t, "image/png", mediaFile.MimeType())
assert.Equal(t, true, mediaFile.IsPng())
})
@ -1165,7 +1165,7 @@ func TestMediaFile_IsTiff(t *testing.T) {
if err != nil {
t.Fatal(err)
}
assert.Equal(t, fs.TypeJson, mediaFile.FileType())
assert.Equal(t, fs.FormatJson, mediaFile.FileType())
assert.Equal(t, "", mediaFile.MimeType())
assert.Equal(t, false, mediaFile.IsTiff())
})
@ -1176,7 +1176,7 @@ func TestMediaFile_IsTiff(t *testing.T) {
if err != nil {
t.Fatal(err)
}
assert.Equal(t, fs.TypeTiff, mediaFile.FileType())
assert.Equal(t, fs.FormatTiff, mediaFile.FileType())
assert.Equal(t, "image/tiff", mediaFile.MimeType())
assert.Equal(t, true, mediaFile.IsTiff())
})
@ -1187,7 +1187,7 @@ func TestMediaFile_IsTiff(t *testing.T) {
if err != nil {
t.Fatal(err)
}
assert.Equal(t, fs.TypeTiff, mediaFile.FileType())
assert.Equal(t, fs.FormatTiff, mediaFile.FileType())
assert.Equal(t, "image/tiff", mediaFile.MimeType())
assert.Equal(t, true, mediaFile.IsTiff())
})
@ -1228,7 +1228,7 @@ func TestMediaFile_IsImageOther(t *testing.T) {
if err != nil {
t.Fatal(err)
}
assert.Equal(t, fs.TypeBitmap, mediaFile.FileType())
assert.Equal(t, fs.FormatBitmap, mediaFile.FileType())
assert.Equal(t, "image/bmp", mediaFile.MimeType())
assert.Equal(t, true, mediaFile.IsBitmap())
assert.Equal(t, true, mediaFile.IsImageOther())
@ -1241,7 +1241,7 @@ func TestMediaFile_IsImageOther(t *testing.T) {
t.Fatal(err)
}
assert.Equal(t, fs.TypeGif, mediaFile.FileType())
assert.Equal(t, fs.FormatGif, mediaFile.FileType())
assert.Equal(t, "image/gif", mediaFile.MimeType())
assert.Equal(t, true, mediaFile.IsImageOther())
})
@ -1837,7 +1837,7 @@ func TestMediaFile_FileType(t *testing.T) {
assert.True(t, m.IsJpeg())
assert.Equal(t, "jpg", string(m.FileType()))
assert.Equal(t, fs.TypeJpeg, m.FileType())
assert.Equal(t, fs.FormatJpeg, m.FileType())
assert.Equal(t, ".png", m.Extension())
}

View file

@ -21,7 +21,7 @@ func (m *MediaFile) MetaData() (result meta.Data) {
}
// Parse regular JSON sidecar files ("img_1234.json").
if jsonFiles := fs.TypeJson.FindAll(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false); len(jsonFiles) == 0 {
if jsonFiles := fs.FormatJson.FindAll(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false); len(jsonFiles) == 0 {
log.Debugf("media: no json sidecar file found for %s", txt.Quote(filepath.Base(m.FileName())))
} else {
for _, jsonFile := range jsonFiles {

View file

@ -11,7 +11,7 @@ func AccountUploads(a entity.Account, limit int) (results entity.Files, err erro
Where("files.id NOT IN (SELECT file_id FROM files_sync WHERE file_id > 0 AND account_id = ?)", a.ID)
if !a.SyncRaw {
s = s.Where("files.file_type <> ? OR files.file_type IS NULL", fs.TypeRaw)
s = s.Where("files.file_type <> ? OR files.file_type IS NULL", fs.FormatRaw)
}
s = s.Order("files.file_name ASC")

View file

@ -15,15 +15,15 @@ import (
"github.com/disintegration/imaging"
)
func ResampleOptions(opts ...ResampleOption) (method ResampleOption, filter imaging.ResampleFilter, format fs.FileType) {
func ResampleOptions(opts ...ResampleOption) (method ResampleOption, filter imaging.ResampleFilter, format fs.FileFormat) {
method = ResampleFit
filter = imaging.Lanczos
format = fs.TypeJpeg
format = fs.FormatJpeg
for _, option := range opts {
switch option {
case ResamplePng:
format = fs.TypePng
format = fs.FormatPng
case ResampleNearestNeighbor:
filter = imaging.NearestNeighbor
case ResampleDefault:
@ -165,7 +165,7 @@ func Create(img image.Image, fileName string, width, height int, opts ...Resampl
var saveOption imaging.EncodeOption
if filepath.Ext(fileName) == "."+string(fs.TypePng) {
if filepath.Ext(fileName) == "."+string(fs.FormatPng) {
saveOption = imaging.PNGCompressionLevel(png.DefaultCompression)
} else if width <= 150 && height <= 150 {
saveOption = imaging.JPEGQuality(JpegQualitySmall)

View file

@ -15,21 +15,21 @@ func TestResampleOptions(t *testing.T) {
assert.Equal(t, ResampleFillCenter, method)
assert.Equal(t, imaging.Lanczos.Support, filter.Support)
assert.Equal(t, fs.TypePng, format)
assert.Equal(t, fs.FormatPng, format)
})
t.Run("ResampleNearestNeighbor, FillTopLeft", func(t *testing.T) {
method, filter, format := ResampleOptions(ResampleNearestNeighbor, ResampleFillTopLeft)
assert.Equal(t, ResampleFillTopLeft, method)
assert.Equal(t, imaging.NearestNeighbor.Support, filter.Support)
assert.Equal(t, fs.TypeJpeg, format)
assert.Equal(t, fs.FormatJpeg, format)
})
t.Run("ResampleNearestNeighbor, FillBottomRight", func(t *testing.T) {
method, filter, format := ResampleOptions(ResampleNearestNeighbor, ResampleFillBottomRight)
assert.Equal(t, ResampleFillBottomRight, method)
assert.Equal(t, imaging.NearestNeighbor.Support, filter.Support)
assert.Equal(t, fs.TypeJpeg, format)
assert.Equal(t, fs.FormatJpeg, format)
})
}

View file

@ -36,7 +36,8 @@ import (
)
type Type struct {
Format fs.FileType
Format fs.FileFormat
Codec fs.FileCodec
Width int
Height int
Public bool
@ -44,14 +45,24 @@ type Type struct {
type TypeMap map[string]Type
var TypeMP4 = Type{
Format: fs.TypeMp4,
var TypeMp4 = Type{
Format: fs.FormatMp4,
Codec: fs.CodecAvc,
Width: 0,
Height: 0,
Public: true,
}
var TypeAvc = Type{
Format: fs.FormatAvc,
Codec: fs.CodecAvc,
Width: 0,
Height: 0,
Public: true,
}
var Types = TypeMap{
"": TypeMP4,
"mp4": TypeMP4,
"": TypeAvc,
"mp4": TypeMp4,
"avc": TypeAvc,
}

View file

@ -3,11 +3,15 @@ package video
import "testing"
func TestTypes(t *testing.T) {
if val := Types[""]; val != TypeMP4 {
t.Fatal("default type should be TypeMP4")
if val := Types[""]; val != TypeAvc {
t.Fatal("default type should be avc")
}
if val := Types["mp4"]; val != TypeMP4 {
t.Fatal("mp4 type should be TypeMP4")
if val := Types["mp4"]; val != TypeMp4 {
t.Fatal("mp4 type should be mp4")
}
if val := Types["avc"]; val != TypeAvc {
t.Fatal("mp4 type should be avc")
}
}

16
pkg/fs/codec.go Normal file
View file

@ -0,0 +1,16 @@
package fs
import (
_ "image/gif" // Import for image.
_ "image/jpeg"
_ "image/png"
)
type FileCodec string
const (
CodecAvc FileCodec = "avc1"
CodecHvc FileCodec = "hvc1"
CodecJpeg FileCodec = "jpeg"
CodecOther FileCodec = ""
)

View file

@ -9,128 +9,129 @@ import (
"strings"
)
type FileType string
type FileFormat string
const (
TypeJpeg FileType = "jpg" // JPEG image file.
TypePng FileType = "png" // PNG image file.
TypeGif FileType = "gif" // GIF image file.
TypeTiff FileType = "tiff" // TIFF image file.
TypeBitmap FileType = "bmp" // BMP image file.
TypeRaw FileType = "raw" // RAW image file.
TypeHEIF FileType = "heif" // High Efficiency Image File Format
TypeMov FileType = "mov" // Video files.
TypeMp4 FileType = "mp4"
TypeHEVC FileType = "hevc"
TypeAvi FileType = "avi"
Type3gp FileType = "3gp"
Type3g2 FileType = "3g2"
TypeFlv FileType = "flv"
TypeMkv FileType = "mkv"
TypeMpg FileType = "mpg"
TypeOgv FileType = "ogv"
TypeWebm FileType = "webm"
TypeWMV FileType = "wmv"
TypeXMP FileType = "xmp" // Adobe XMP sidecar file (XML).
TypeAAE FileType = "aae" // Apple sidecar file (XML).
TypeXML FileType = "xml" // XML metadata / config / sidecar file.
TypeYaml FileType = "yml" // YAML metadata / config / sidecar file.
TypeToml FileType = "toml" // Tom's Obvious, Minimal Language sidecar file.
TypeJson FileType = "json" // JSON metadata / config / sidecar file.
TypeText FileType = "txt" // Text config / sidecar file.
TypeMarkdown FileType = "md" // Markdown text sidecar file.
TypeOther FileType = "" // Unknown file format.
FormatJpeg FileFormat = "jpg" // JPEG image file.
FormatPng FileFormat = "png" // PNG image file.
FormatGif FileFormat = "gif" // GIF image file.
FormatTiff FileFormat = "tiff" // TIFF image file.
FormatBitmap FileFormat = "bmp" // BMP image file.
FormatRaw FileFormat = "raw" // RAW image file.
FormatHEIF FileFormat = "heif" // High Efficiency Image File Format
FormatHEVC FileFormat = "hevc"
FormatMov FileFormat = "mov" // Video files.
FormatMp4 FileFormat = "mp4"
FormatAvc FileFormat = "avc"
FormatAvi FileFormat = "avi"
Format3gp FileFormat = "3gp"
Format3g2 FileFormat = "3g2"
FormatFlv FileFormat = "flv"
FormatMkv FileFormat = "mkv"
FormatMpg FileFormat = "mpg"
FormatOgv FileFormat = "ogv"
FormatWebm FileFormat = "webm"
FormatWMV FileFormat = "wmv"
FormatXMP FileFormat = "xmp" // Adobe XMP sidecar file (XML).
FormatAAE FileFormat = "aae" // Apple sidecar file (XML).
FormatXML FileFormat = "xml" // XML metadata / config / sidecar file.
FormatYaml FileFormat = "yml" // YAML metadata / config / sidecar file.
FormatToml FileFormat = "toml" // Tom's Obvious, Minimal Language sidecar file.
FormatJson FileFormat = "json" // JSON metadata / config / sidecar file.
FormatText FileFormat = "txt" // Text config / sidecar file.
FormatMarkdown FileFormat = "md" // Markdown text sidecar file.
FormatOther FileFormat = "" // Unknown file format.
)
type FileExtensions map[string]FileType
type TypeExtensions map[FileType][]string
type FileExtensions map[string]FileFormat
type TypeExtensions map[FileFormat][]string
const (
YamlExt = ".yml"
JpegExt = ".jpg"
AvcExt = ".mp4"
HevcExt = ".hevc"
AvcExt = ".avc"
)
// FileExt contains the filename extensions of file formats known to PhotoPrism.
var FileExt = FileExtensions{
".bmp": TypeBitmap,
".gif": TypeGif,
".tif": TypeTiff,
".tiff": TypeTiff,
".png": TypePng,
".pn": TypePng,
".crw": TypeRaw,
".cr2": TypeRaw,
".cr3": TypeRaw,
".nef": TypeRaw,
".arw": TypeRaw,
".dng": TypeRaw,
".mov": TypeMov,
".avi": TypeAvi,
".mp4": TypeMp4,
".hevc": TypeHEVC,
".3gp": Type3gp,
".3g2": Type3g2,
".flv": TypeFlv,
".mkv": TypeMkv,
".mpg": TypeMpg,
".mpeg": TypeMpg,
".ogv": TypeOgv,
".webm": TypeWebm,
".wmv": TypeWMV,
".yml": TypeYaml,
".yaml": TypeYaml,
".jpg": TypeJpeg,
".jpeg": TypeJpeg,
".jpe": TypeJpeg,
".jif": TypeJpeg,
".jfif": TypeJpeg,
".jfi": TypeJpeg,
".thm": TypeJpeg,
".xmp": TypeXMP,
".aae": TypeAAE,
".heif": TypeHEIF,
".heic": TypeHEIF,
".3fr": TypeRaw,
".ari": TypeRaw,
".bay": TypeRaw,
".cap": TypeRaw,
".data": TypeRaw,
".dcs": TypeRaw,
".dcr": TypeRaw,
".drf": TypeRaw,
".eip": TypeRaw,
".erf": TypeRaw,
".fff": TypeRaw,
".gpr": TypeRaw,
".iiq": TypeRaw,
".k25": TypeRaw,
".kdc": TypeRaw,
".mdc": TypeRaw,
".mef": TypeRaw,
".mos": TypeRaw,
".mrw": TypeRaw,
".nrw": TypeRaw,
".obm": TypeRaw,
".orf": TypeRaw,
".pef": TypeRaw,
".ptx": TypeRaw,
".pxn": TypeRaw,
".r3d": TypeRaw,
".raf": TypeRaw,
".raw": TypeRaw,
".rwl": TypeRaw,
".rw2": TypeRaw,
".rwz": TypeRaw,
".sr2": TypeRaw,
".srf": TypeRaw,
".srw": TypeRaw,
".x3f": TypeRaw,
".xml": TypeXML,
".txt": TypeText,
".md": TypeMarkdown,
".json": TypeJson,
".bmp": FormatBitmap,
".gif": FormatGif,
".tif": FormatTiff,
".tiff": FormatTiff,
".png": FormatPng,
".pn": FormatPng,
".crw": FormatRaw,
".cr2": FormatRaw,
".cr3": FormatRaw,
".nef": FormatRaw,
".arw": FormatRaw,
".dng": FormatRaw,
".mov": FormatMov,
".avi": FormatAvi,
".mp4": FormatMp4,
".avc": FormatAvc,
".hevc": FormatHEVC,
".3gp": Format3gp,
".3g2": Format3g2,
".flv": FormatFlv,
".mkv": FormatMkv,
".mpg": FormatMpg,
".mpeg": FormatMpg,
".ogv": FormatOgv,
".webm": FormatWebm,
".wmv": FormatWMV,
".yml": FormatYaml,
".yaml": FormatYaml,
".jpg": FormatJpeg,
".jpeg": FormatJpeg,
".jpe": FormatJpeg,
".jif": FormatJpeg,
".jfif": FormatJpeg,
".jfi": FormatJpeg,
".thm": FormatJpeg,
".xmp": FormatXMP,
".aae": FormatAAE,
".heif": FormatHEIF,
".heic": FormatHEIF,
".3fr": FormatRaw,
".ari": FormatRaw,
".bay": FormatRaw,
".cap": FormatRaw,
".data": FormatRaw,
".dcs": FormatRaw,
".dcr": FormatRaw,
".drf": FormatRaw,
".eip": FormatRaw,
".erf": FormatRaw,
".fff": FormatRaw,
".gpr": FormatRaw,
".iiq": FormatRaw,
".k25": FormatRaw,
".kdc": FormatRaw,
".mdc": FormatRaw,
".mef": FormatRaw,
".mos": FormatRaw,
".mrw": FormatRaw,
".nrw": FormatRaw,
".obm": FormatRaw,
".orf": FormatRaw,
".pef": FormatRaw,
".ptx": FormatRaw,
".pxn": FormatRaw,
".r3d": FormatRaw,
".raf": FormatRaw,
".raw": FormatRaw,
".rwl": FormatRaw,
".rw2": FormatRaw,
".rwz": FormatRaw,
".sr2": FormatRaw,
".srf": FormatRaw,
".srw": FormatRaw,
".x3f": FormatRaw,
".xml": FormatXML,
".txt": FormatText,
".md": FormatMarkdown,
".json": FormatJson,
}
func (m FileExtensions) Known(name string) bool {
@ -169,7 +170,7 @@ func (m FileExtensions) TypeExt() TypeExtensions {
var TypeExt = FileExt.TypeExt()
// Find returns the first filename with the same base name and a given type.
func (t FileType) Find(fileName string, stripSequence bool) string {
func (t FileFormat) Find(fileName string, stripSequence bool) string {
base := BasePrefix(fileName, stripSequence)
dir := filepath.Dir(fileName)
@ -194,20 +195,20 @@ func (t FileType) Find(fileName string, stripSequence bool) string {
return ""
}
// GetFileType returns the (expected) type for a given file name.
func GetFileType(fileName string) FileType {
// GetFileFormat returns the (expected) type for a given file name.
func GetFileFormat(fileName string) FileFormat {
fileExt := strings.ToLower(filepath.Ext(fileName))
result, ok := FileExt[fileExt]
if !ok {
result = TypeOther
result = FormatOther
}
return result
}
// FindFirst searches a list of directories for the first file with the same base name and a given type.
func (t FileType) FindFirst(fileName string, dirs []string, baseDir string, stripSequence bool) string {
func (t FileFormat) FindFirst(fileName string, dirs []string, baseDir string, stripSequence bool) string {
fileBase := filepath.Base(fileName)
fileBasePrefix := BasePrefix(fileName, stripSequence)
fileBaseLower := strings.ToLower(fileBasePrefix)
@ -256,7 +257,7 @@ func (t FileType) FindFirst(fileName string, dirs []string, baseDir string, stri
}
// FindAll searches a list of directories for files with the same base name and a given type.
func (t FileType) FindAll(fileName string, dirs []string, baseDir string, stripSequence bool) (results []string) {
func (t FileFormat) FindAll(fileName string, dirs []string, baseDir string, stripSequence bool) (results []string) {
fileBase := filepath.Base(fileName)
fileBasePrefix := BasePrefix(fileName, stripSequence)
fileBaseLower := strings.ToLower(fileBasePrefix)

View file

@ -8,49 +8,49 @@ import (
func TestGetFileType(t *testing.T) {
t.Run("jpeg", func(t *testing.T) {
result := GetFileType("testdata/test.jpg")
assert.Equal(t, TypeJpeg, result)
result := GetFileFormat("testdata/test.jpg")
assert.Equal(t, FormatJpeg, result)
})
t.Run("raw", func(t *testing.T) {
result := GetFileType("testdata/test (jpg).CR2")
assert.Equal(t, TypeRaw, result)
result := GetFileFormat("testdata/test (jpg).CR2")
assert.Equal(t, FormatRaw, result)
})
t.Run("empty", func(t *testing.T) {
result := GetFileType("")
assert.Equal(t, TypeOther, result)
result := GetFileFormat("")
assert.Equal(t, FormatOther, result)
})
}
func TestFileType_Find(t *testing.T) {
t.Run("find jpg", func(t *testing.T) {
result := TypeJpeg.Find("testdata/test.xmp", false)
result := FormatJpeg.Find("testdata/test.xmp", false)
assert.Equal(t, "testdata/test.jpg", result)
})
t.Run("upper ext", func(t *testing.T) {
result := TypeJpeg.Find("testdata/test.XMP", false)
result := FormatJpeg.Find("testdata/test.XMP", false)
assert.Equal(t, "testdata/test.jpg", result)
})
t.Run("with sequence", func(t *testing.T) {
result := TypeJpeg.Find("testdata/test (2).xmp", false)
result := FormatJpeg.Find("testdata/test (2).xmp", false)
assert.Equal(t, "", result)
})
t.Run("strip sequence", func(t *testing.T) {
result := TypeJpeg.Find("testdata/test (2).xmp", true)
result := FormatJpeg.Find("testdata/test (2).xmp", true)
assert.Equal(t, "testdata/test.jpg", result)
})
t.Run("name upper", func(t *testing.T) {
result := TypeJpeg.Find("testdata/CATYELLOW.xmp", true)
result := FormatJpeg.Find("testdata/CATYELLOW.xmp", true)
assert.Equal(t, "testdata/CATYELLOW.jpg", result)
})
t.Run("name lower", func(t *testing.T) {
result := TypeJpeg.Find("testdata/chameleon_lime.xmp", true)
result := FormatJpeg.Find("testdata/chameleon_lime.xmp", true)
assert.Equal(t, "testdata/chameleon_lime.jpg", result)
})
}
@ -59,77 +59,77 @@ func TestFileType_FindFirst(t *testing.T) {
dirs := []string{HiddenPath}
t.Run("find xmp", func(t *testing.T) {
result := TypeXMP.FindFirst("testdata/test.jpg", dirs, "", false)
result := FormatXMP.FindFirst("testdata/test.jpg", dirs, "", false)
assert.Equal(t, "testdata/.photoprism/test.xmp", result)
})
t.Run("find xmp upper ext", func(t *testing.T) {
result := TypeXMP.FindFirst("testdata/test.PNG", dirs, "", false)
result := FormatXMP.FindFirst("testdata/test.PNG", dirs, "", false)
assert.Equal(t, "testdata/.photoprism/test.xmp", result)
})
t.Run("find xmp without sequence", func(t *testing.T) {
result := TypeXMP.FindFirst("testdata/test (2).jpg", dirs, "", false)
result := FormatXMP.FindFirst("testdata/test (2).jpg", dirs, "", false)
assert.Equal(t, "", result)
})
t.Run("find xmp with sequence", func(t *testing.T) {
result := TypeXMP.FindFirst("testdata/test (2).jpg", dirs, "", true)
result := FormatXMP.FindFirst("testdata/test (2).jpg", dirs, "", true)
assert.Equal(t, "testdata/.photoprism/test.xmp", result)
})
t.Run("find jpg", func(t *testing.T) {
result := TypeJpeg.FindFirst("testdata/test.xmp", dirs, "", false)
result := FormatJpeg.FindFirst("testdata/test.xmp", dirs, "", false)
assert.Equal(t, "testdata/test.jpg", result)
})
t.Run("find jpg abs", func(t *testing.T) {
result := TypeJpeg.FindFirst(Abs("testdata/test.xmp"), dirs, "", false)
result := FormatJpeg.FindFirst(Abs("testdata/test.xmp"), dirs, "", false)
assert.Equal(t, Abs("testdata/test.jpg"), result)
})
t.Run("upper ext", func(t *testing.T) {
result := TypeJpeg.FindFirst("testdata/test.XMP", dirs, "", false)
result := FormatJpeg.FindFirst("testdata/test.XMP", dirs, "", false)
assert.Equal(t, "testdata/test.jpg", result)
})
t.Run("with sequence", func(t *testing.T) {
result := TypeJpeg.FindFirst("testdata/test (2).xmp", dirs, "", false)
result := FormatJpeg.FindFirst("testdata/test (2).xmp", dirs, "", false)
assert.Equal(t, "", result)
})
t.Run("strip sequence", func(t *testing.T) {
result := TypeJpeg.FindFirst("testdata/test (2).xmp", dirs, "", true)
result := FormatJpeg.FindFirst("testdata/test (2).xmp", dirs, "", true)
assert.Equal(t, "testdata/test.jpg", result)
})
t.Run("name upper", func(t *testing.T) {
result := TypeJpeg.FindFirst("testdata/CATYELLOW.xmp", dirs, "", true)
result := FormatJpeg.FindFirst("testdata/CATYELLOW.xmp", dirs, "", true)
assert.Equal(t, "testdata/CATYELLOW.jpg", result)
})
t.Run("name lower", func(t *testing.T) {
result := TypeJpeg.FindFirst("testdata/chameleon_lime.xmp", dirs, "", true)
result := FormatJpeg.FindFirst("testdata/chameleon_lime.xmp", dirs, "", true)
assert.Equal(t, "testdata/chameleon_lime.jpg", result)
})
t.Run("example_bmp_notfound", func(t *testing.T) {
result := TypeBitmap.FindFirst("testdata/example.00001.jpg", dirs, "", true)
result := FormatBitmap.FindFirst("testdata/example.00001.jpg", dirs, "", true)
assert.Equal(t, "", result)
})
t.Run("example_bmp_found", func(t *testing.T) {
result := TypeBitmap.FindFirst("testdata/example.00001.jpg", []string{"directory"}, "", true)
result := FormatBitmap.FindFirst("testdata/example.00001.jpg", []string{"directory"}, "", true)
assert.Equal(t, "testdata/directory/example.bmp", result)
})
t.Run("example_png_found", func(t *testing.T) {
result := TypePng.FindFirst("testdata/example.00001.jpg", []string{"directory", "directory/subdirectory"}, "", true)
result := FormatPng.FindFirst("testdata/example.00001.jpg", []string{"directory", "directory/subdirectory"}, "", true)
assert.Equal(t, "testdata/directory/subdirectory/example.png", result)
})
t.Run("example_bmp_found", func(t *testing.T) {
result := TypeBitmap.FindFirst(Abs("testdata/example.00001.jpg"), []string{"directory"}, Abs("testdata"), true)
result := FormatBitmap.FindFirst(Abs("testdata/example.00001.jpg"), []string{"directory"}, Abs("testdata"), true)
assert.Equal(t, Abs("testdata/directory/example.bmp"), result)
})
}

View file

@ -10,35 +10,36 @@ const (
MediaOther MediaType = "other"
)
var MediaTypes = map[FileType]MediaType{
TypeRaw: MediaRaw,
TypeJpeg: MediaImage,
TypePng: MediaImage,
TypeGif: MediaImage,
TypeTiff: MediaImage,
TypeBitmap: MediaImage,
TypeHEIF: MediaImage,
TypeAvi: MediaVideo,
TypeHEVC: MediaVideo,
TypeMp4: MediaVideo,
TypeMov: MediaVideo,
Type3gp: MediaVideo,
Type3g2: MediaVideo,
TypeFlv: MediaVideo,
TypeMkv: MediaVideo,
TypeMpg: MediaVideo,
TypeOgv: MediaVideo,
TypeWebm: MediaVideo,
TypeWMV: MediaVideo,
TypeXMP: MediaSidecar,
TypeXML: MediaSidecar,
TypeAAE: MediaSidecar,
TypeYaml: MediaSidecar,
TypeText: MediaSidecar,
TypeJson: MediaSidecar,
TypeToml: MediaSidecar,
TypeMarkdown: MediaSidecar,
TypeOther: MediaOther,
var MediaTypes = map[FileFormat]MediaType{
FormatRaw: MediaRaw,
FormatJpeg: MediaImage,
FormatPng: MediaImage,
FormatGif: MediaImage,
FormatTiff: MediaImage,
FormatBitmap: MediaImage,
FormatHEIF: MediaImage,
FormatAvi: MediaVideo,
FormatHEVC: MediaVideo,
FormatAvc: MediaVideo,
FormatMp4: MediaVideo,
FormatMov: MediaVideo,
Format3gp: MediaVideo,
Format3g2: MediaVideo,
FormatFlv: MediaVideo,
FormatMkv: MediaVideo,
FormatMpg: MediaVideo,
FormatOgv: MediaVideo,
FormatWebm: MediaVideo,
FormatWMV: MediaVideo,
FormatXMP: MediaSidecar,
FormatXML: MediaSidecar,
FormatAAE: MediaSidecar,
FormatYaml: MediaSidecar,
FormatText: MediaSidecar,
FormatJson: MediaSidecar,
FormatToml: MediaSidecar,
FormatMarkdown: MediaSidecar,
FormatOther: MediaOther,
}
func GetMediaType(fileName string) MediaType {
@ -46,7 +47,7 @@ func GetMediaType(fileName string) MediaType {
return MediaOther
}
result, ok := MediaTypes[GetFileType(fileName)]
result, ok := MediaTypes[GetFileFormat(fileName)]
if !ok {
result = MediaOther

View file

@ -77,7 +77,7 @@ func TestSkipWalk(t *testing.T) {
done[fileName] = Found
if textName := TypeText.Find(fileName, false); textName != "" {
if textName := FormatText.Find(fileName, false); textName != "" {
done[textName] = Found
}