-
Notifications
You must be signed in to change notification settings - Fork 0
/
lglivephoto.go
127 lines (98 loc) · 3.07 KB
/
lglivephoto.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// Package lglivephoto provides functions to unpack or pack LG Live Photo.
// LG Live Photo is a functionality in LG smartphones
// where actions before and after taking a photo is recorded with the photo.
// (see: https://www.lg.com/uk/support/product-help/CT00008356-20150844039308)
package lglivephoto // import "github.com/ryanking13/lglivephoto"
import (
"encoding/binary"
"errors"
"fmt"
"io/ioutil"
"go.uber.org/zap/zapcore"
)
// Unpack takes a Live Photo file and extracts it to
// image and video embedded in the photo.
func Unpack(imagePath string) ([]byte, []byte, error) {
logger.Debugf("Start unpacking: %s", imagePath)
data, err := ioutil.ReadFile(imagePath)
if err != nil {
return nil, nil, err
}
idx, err := findVideoIndex(data)
if err != nil {
return nil, nil, err
}
logger.Debugf("Video index of %s = %x", imagePath, idx)
return data[:idx], data[idx:], nil
}
// Pack generates LG Live Photo by embedding the mp4 video to the image,
func Pack(imagePath string, videoPath string) ([]byte, error) {
logger.Debugf("Start packing: %s to %s", videoPath, imagePath)
imageData, err := ioutil.ReadFile(imagePath)
if err != nil {
return nil, err
}
videoData, err := ioutil.ReadFile(videoPath)
if err != nil {
return nil, err
}
if !isJPEG(imageData[0:2]) {
return nil, fmt.Errorf("%s is not a JPEG file", imagePath)
}
if !isMP4(videoData[0:8]) {
return nil, fmt.Errorf("%s is not a MP4 file", videoPath)
}
_, err = findVideoIndex(imageData)
if err == nil {
return nil, fmt.Errorf("%s is already a Live Photo", imagePath)
}
return append(imageData, videoData...), nil
}
// IsLivePhoto checks whether given image file is a live photo or not.
func IsLivePhoto(imagePath string) (bool, error) {
logger.Debugf("Checking whether %s is a live photo", imagePath)
data, err := ioutil.ReadFile(imagePath)
if err != nil {
return false, err
}
_, err = findVideoIndex(data)
if err != nil {
return false, err
}
return true, nil
}
// Debug changes logger verbosity, which is mostly used for debugging.
func Debug(debug bool) {
if debug {
atom.SetLevel(zapcore.DebugLevel)
} else {
atom.SetLevel(zapcore.InfoLevel)
}
}
func findVideoIndex(data []byte) (int, error) {
if !isJPEG(data[0:2]) {
return -1, errors.New("not a JPEG format")
}
appIdx := 2
for isJPEGMarker(data[appIdx:appIdx+2]) && !isSOS(data[appIdx:appIdx+2]) {
segmentSize := binary.BigEndian.Uint16(data[appIdx+2 : appIdx+4])
appIdx += int(segmentSize) + 2
// fmt.Printf("%x %x %x\n", segmentSize, appIdx, data[appIdx:appIdx+2])
}
// https://stackoverflow.com/questions/26715684/parsing-jpeg-sos-marker
eoiIdx := appIdx + 2
for !isEOI(data[eoiIdx : eoiIdx+2]) {
eoiIdx++
}
videoStartIdx := eoiIdx + 2
if videoStartIdx >= len(data) {
return -1, errors.New("not a live photo")
}
if !isMP4(data[videoStartIdx : videoStartIdx+4]) {
return -1, errors.New(
"there is a chunck after JPEG image, but it is not a MP4 file, maybe the image is corrupted. " +
"Please report the problem to: https://github.com/ryanking13/lglivephoto/issues",
)
}
return videoStartIdx, nil
}