Convert: Add --force flag to replace JPEGs in the sidecar folder #2214
This commit is contained in:
parent
0838a71e6e
commit
4be948c774
15 changed files with 68 additions and 31 deletions
|
@ -16,9 +16,15 @@ import (
|
|||
// ConvertCommand registers the convert cli command.
|
||||
var ConvertCommand = cli.Command{
|
||||
Name: "convert",
|
||||
Usage: "Converts files in other formats to JPEG and AVC",
|
||||
ArgsUsage: "[ORIGINALS SUB-FOLDER]",
|
||||
Action: convertAction,
|
||||
Usage: "Converts files in other formats to JPEG and AVC as needed",
|
||||
ArgsUsage: "[originals folder]",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "replace existing JPEG files in the sidecar folder",
|
||||
},
|
||||
},
|
||||
Action: convertAction,
|
||||
}
|
||||
|
||||
// convertAction converts originals in other formats to JPEG and AVC sidecar files.
|
||||
|
@ -52,7 +58,8 @@ func convertAction(ctx *cli.Context) error {
|
|||
|
||||
w := service.Convert()
|
||||
|
||||
if err := w.Start(convertPath); err != nil {
|
||||
// Start file conversion.
|
||||
if err := w.Start(convertPath, ctx.Bool("force")); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ var CopyCommand = cli.Command{
|
|||
Name: "cp",
|
||||
Aliases: []string{"copy"},
|
||||
Usage: "Copies media files to originals",
|
||||
ArgsUsage: "[PATH]",
|
||||
ArgsUsage: "[path]",
|
||||
Action: copyAction,
|
||||
}
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ var FacesCommand = cli.Command{
|
|||
{
|
||||
Name: "index",
|
||||
Usage: "Searches originals for faces",
|
||||
ArgsUsage: "[ORIGINALS SUB-FOLDER]",
|
||||
ArgsUsage: "[originals folder]",
|
||||
Action: facesIndexAction,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -19,7 +19,7 @@ var ImportCommand = cli.Command{
|
|||
Name: "mv",
|
||||
Aliases: []string{"import"},
|
||||
Usage: "Moves media files to originals",
|
||||
ArgsUsage: "[PATH]",
|
||||
ArgsUsage: "[path]",
|
||||
Action: importAction,
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ import (
|
|||
var IndexCommand = cli.Command{
|
||||
Name: "index",
|
||||
Usage: "Indexes original media files",
|
||||
ArgsUsage: "[ORIGINALS SUB-FOLDER]",
|
||||
ArgsUsage: "[originals folder]",
|
||||
Flags: indexFlags,
|
||||
Action: indexAction,
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ var RestoreCommand = cli.Command{
|
|||
Name: "restore",
|
||||
Description: restoreDescription,
|
||||
Usage: "Restores the index from an SQL dump and optionally albums from YAML files",
|
||||
ArgsUsage: "[FILENAME]",
|
||||
ArgsUsage: "[filename.sql]",
|
||||
Flags: restoreFlags,
|
||||
Action: restoreAction,
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ var UsersCommand = cli.Command{
|
|||
Name: "delete",
|
||||
Usage: "Removes an existing user",
|
||||
Action: usersDeleteAction,
|
||||
ArgsUsage: "[USERNAME]",
|
||||
ArgsUsage: "[username]",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ func NewConvert(conf *config.Config) *Convert {
|
|||
}
|
||||
|
||||
// Start converts all files in a directory to JPEG if possible.
|
||||
func (c *Convert) Start(path string) (err error) {
|
||||
func (c *Convert) Start(path string, force bool) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("convert: %s (panic)\nstack: %s", r, debug.Stack())
|
||||
|
@ -114,6 +114,7 @@ func (c *Convert) Start(path string) (err error) {
|
|||
done[fileName] = fs.Processed
|
||||
|
||||
jobs <- ConvertJob{
|
||||
force: force,
|
||||
file: f,
|
||||
convert: c,
|
||||
}
|
||||
|
@ -245,7 +246,7 @@ func (c *Convert) JpegConvertCommand(f *MediaFile, jpegName string, xmpName stri
|
|||
}
|
||||
|
||||
// ToJpeg converts a single image file to JPEG if possible.
|
||||
func (c *Convert) ToJpeg(f *MediaFile) (*MediaFile, error) {
|
||||
func (c *Convert) ToJpeg(f *MediaFile, force bool) (*MediaFile, error) {
|
||||
if f == nil {
|
||||
return nil, fmt.Errorf("convert: file is nil - you might have found a bug")
|
||||
}
|
||||
|
@ -262,17 +263,26 @@ func (c *Convert) ToJpeg(f *MediaFile) (*MediaFile, error) {
|
|||
|
||||
mediaFile, err := NewMediaFile(jpegName)
|
||||
|
||||
// Replace existing sidecar if "force" is true.
|
||||
if err == nil && mediaFile.IsJpeg() {
|
||||
return mediaFile, nil
|
||||
if force && mediaFile.InSidecar() {
|
||||
if err := mediaFile.Remove(); err != nil {
|
||||
return mediaFile, fmt.Errorf("convert: failed removing %s (%s)", mediaFile.RootRelName(), err)
|
||||
} else {
|
||||
log.Infof("convert: replacing %s", sanitize.Log(mediaFile.RootRelName()))
|
||||
}
|
||||
} else {
|
||||
return mediaFile, nil
|
||||
}
|
||||
} else {
|
||||
jpegName = fs.FileName(f.FileName(), c.conf.SidecarPath(), c.conf.OriginalsPath(), fs.JpegExt)
|
||||
}
|
||||
|
||||
if !c.conf.SidecarWritable() {
|
||||
return nil, fmt.Errorf("convert: disabled in read only mode (%s)", f.RelName(c.conf.OriginalsPath()))
|
||||
}
|
||||
|
||||
jpegName = fs.FileName(f.FileName(), c.conf.SidecarPath(), c.conf.OriginalsPath(), fs.JpegExt)
|
||||
fileName := f.RelName(c.conf.OriginalsPath())
|
||||
|
||||
xmpName := fs.FormatXMP.Find(f.FileName(), false)
|
||||
|
||||
event.Publish("index.converting", event.Data{
|
||||
|
@ -285,7 +295,7 @@ func (c *Convert) ToJpeg(f *MediaFile) (*MediaFile, error) {
|
|||
start := time.Now()
|
||||
|
||||
if f.IsImageOther() {
|
||||
log.Infof("%s: converting %s to %s", f.FileType(), fileName, fs.FormatJpeg)
|
||||
log.Infof("convert: converting %s to %s (%s)", sanitize.Log(filepath.Base(fileName)), sanitize.Log(filepath.Base(jpegName)), f.FileType())
|
||||
|
||||
_, err = thumb.Jpeg(f.FileName(), jpegName, f.Orientation())
|
||||
|
||||
|
@ -293,7 +303,7 @@ func (c *Convert) ToJpeg(f *MediaFile) (*MediaFile, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
log.Infof("%s: created %s [%s]", f.FileType(), filepath.Base(jpegName), time.Since(start))
|
||||
log.Infof("convert: %s created in %s (%s)", sanitize.Log(filepath.Base(jpegName)), time.Since(start), f.FileType())
|
||||
|
||||
return NewMediaFile(jpegName)
|
||||
}
|
||||
|
@ -321,7 +331,7 @@ func (c *Convert) ToJpeg(f *MediaFile) (*MediaFile, error) {
|
|||
cmd.Stdout = &out
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
log.Infof("%s: converting %s to %s", filepath.Base(cmd.Path), fileName, fs.FormatJpeg)
|
||||
log.Infof("convert: converting %s to %s (%s)", sanitize.Log(filepath.Base(fileName)), sanitize.Log(filepath.Base(jpegName)), filepath.Base(cmd.Path))
|
||||
|
||||
// Log exact command for debugging in trace mode.
|
||||
log.Trace(cmd.String())
|
||||
|
@ -335,7 +345,7 @@ func (c *Convert) ToJpeg(f *MediaFile) (*MediaFile, error) {
|
|||
}
|
||||
}
|
||||
|
||||
log.Infof("%s: created %s [%s]", filepath.Base(cmd.Path), filepath.Base(jpegName), time.Since(start))
|
||||
log.Infof("convert: %s created in %s (%s)", sanitize.Log(filepath.Base(jpegName)), time.Since(start), filepath.Base(cmd.Path))
|
||||
|
||||
return NewMediaFile(jpegName)
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ func TestConvert_ToJpeg(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
jpegFile, err := convert.ToJpeg(mf)
|
||||
jpegFile, err := convert.ToJpeg(mf, false)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -68,7 +68,7 @@ func TestConvert_ToJpeg(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
imageJpeg, err := convert.ToJpeg(mf)
|
||||
imageJpeg, err := convert.ToJpeg(mf, false)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -91,7 +91,7 @@ func TestConvert_ToJpeg(t *testing.T) {
|
|||
t.Fatalf("%s for %s", err.Error(), rawFilename)
|
||||
}
|
||||
|
||||
imageRaw, err := convert.ToJpeg(rawMediaFile)
|
||||
imageRaw, err := convert.ToJpeg(rawMediaFile, false)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("%s for %s", err.Error(), rawFilename)
|
||||
|
@ -206,7 +206,7 @@ func TestConvert_Start(t *testing.T) {
|
|||
|
||||
convert := NewConvert(conf)
|
||||
|
||||
err := convert.Start(conf.ImportPath())
|
||||
err := convert.Start(conf.ImportPath(), false)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -234,7 +234,7 @@ func TestConvert_Start(t *testing.T) {
|
|||
|
||||
_ = os.Remove(existingJpegFilename)
|
||||
|
||||
if err := convert.Start(conf.ImportPath()); err != nil {
|
||||
if err := convert.Start(conf.ImportPath(), false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
)
|
||||
|
||||
type ConvertJob struct {
|
||||
force bool
|
||||
file *MediaFile
|
||||
convert *Convert
|
||||
}
|
||||
|
@ -26,7 +27,8 @@ func ConvertWorker(jobs <-chan ConvertJob) {
|
|||
case job.file.IsVideo():
|
||||
_, _ = job.convert.ToJson(job.file)
|
||||
|
||||
if _, err := job.convert.ToJpeg(job.file); err != nil {
|
||||
// Create JPEG preview and AVC encoded version for videos.
|
||||
if _, err := job.convert.ToJpeg(job.file, job.force); err != nil {
|
||||
logError(err, job)
|
||||
} else if metaData := job.file.MetaData(); metaData.CodecAvc() {
|
||||
continue
|
||||
|
@ -34,7 +36,7 @@ func ConvertWorker(jobs <-chan ConvertJob) {
|
|||
logError(err, job)
|
||||
}
|
||||
default:
|
||||
if _, err := job.convert.ToJpeg(job.file); err != nil {
|
||||
if _, err := job.convert.ToJpeg(job.file, job.force); err != nil {
|
||||
logError(err, job)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -135,7 +135,7 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
|
||||
// Create JPEG sidecar for media files in other formats so that thumbnails can be created.
|
||||
if o.Convert && f.IsMedia() && !f.HasJpeg() {
|
||||
if jpegFile, err := imp.convert.ToJpeg(f); err != nil {
|
||||
if jpegFile, err := imp.convert.ToJpeg(f, false); err != nil {
|
||||
log.Errorf("import: %s in %s (convert to jpeg)", err.Error(), sanitize.Log(f.RootRelName()))
|
||||
continue
|
||||
} else {
|
||||
|
|
|
@ -42,7 +42,7 @@ func IndexMain(related *RelatedFiles, ind *Index, o IndexOptions) (result IndexR
|
|||
|
||||
// Create JPEG sidecar for media files in other formats so that thumbnails can be created.
|
||||
if o.Convert && f.IsMedia() && !f.HasJpeg() {
|
||||
if jpg, err := ind.convert.ToJpeg(f); err != nil {
|
||||
if jpg, err := ind.convert.ToJpeg(f, false); err != nil {
|
||||
result.Err = fmt.Errorf("index: failed converting %s to jpeg (%s)", sanitize.Log(f.RootRelName()), err.Error())
|
||||
result.Status = IndexFailed
|
||||
return result
|
||||
|
|
|
@ -75,7 +75,7 @@ func IndexRelated(related RelatedFiles, ind *Index, o IndexOptions) (result Inde
|
|||
|
||||
// Create JPEG sidecar for media files in other formats so that thumbnails can be created.
|
||||
if o.Convert && f.IsMedia() && !f.HasJpeg() {
|
||||
if jpg, err := ind.convert.ToJpeg(f); err != nil {
|
||||
if jpg, err := ind.convert.ToJpeg(f, false); err != nil {
|
||||
result.Err = fmt.Errorf("index: failed converting %s to jpeg (%s)", sanitize.Log(f.RootRelName()), err.Error())
|
||||
result.Status = IndexFailed
|
||||
return result
|
||||
|
|
|
@ -752,7 +752,17 @@ func (m *MediaFile) IsXMP() bool {
|
|||
return m.FileType() == fs.FormatXMP
|
||||
}
|
||||
|
||||
// IsSidecar returns true if this is a sidecar file (containing metadata).
|
||||
// InOriginals checks if the file is stored in the 'originals' folder.
|
||||
func (m *MediaFile) InOriginals() bool {
|
||||
return m.Root() == entity.RootOriginals
|
||||
}
|
||||
|
||||
// InSidecar checks if the file is stored in the 'sidecar' folder.
|
||||
func (m *MediaFile) InSidecar() bool {
|
||||
return m.Root() == entity.RootSidecar
|
||||
}
|
||||
|
||||
// IsSidecar checks if the file is a metadata sidecar file, independent of the storage location.
|
||||
func (m *MediaFile) IsSidecar() bool {
|
||||
return m.MediaType() == fs.MediaSidecar
|
||||
}
|
||||
|
|
|
@ -325,7 +325,15 @@ func TestMediaFile_Exif_HEIF(t *testing.T) {
|
|||
|
||||
convert := NewConvert(conf)
|
||||
|
||||
jpeg, err := convert.ToJpeg(img)
|
||||
// Create JPEG.
|
||||
jpeg, err := convert.ToJpeg(img, false)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Replace JPEG.
|
||||
jpeg, err = convert.ToJpeg(img, true)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
Loading…
Reference in a new issue