| 
									
										
										
										
											2024-08-02 11:46:41 +00:00
										 |  |  | // GoToSocial | 
					
						
							|  |  |  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-or-later | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // 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. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package media | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	terminator "codeberg.org/superseriousbusiness/exif-terminator" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/log" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // clearMetadata performs our best-attempt at cleaning metadata from | 
					
						
							|  |  |  | // input file. Depending on file type this may perform a full EXIF clean, | 
					
						
							|  |  |  | // or just a clean of global container level metadata records. | 
					
						
							|  |  |  | func clearMetadata(ctx context.Context, filepath string) error { | 
					
						
							|  |  |  | 	var ext, outpath string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Generate cleaned output path MAINTAINING extension. | 
					
						
							|  |  |  | 	if i := strings.IndexByte(filepath, '.'); i != -1 { | 
					
						
							|  |  |  | 		outpath = filepath[:i] + "_cleaned" + filepath[i:] | 
					
						
							|  |  |  | 		ext = filepath[i+1:] | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		return gtserror.New("input file missing extension") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch ext { | 
					
						
							|  |  |  | 	case "jpeg", "png", "webp": | 
					
						
							|  |  |  | 		// For these few file types, we actually support | 
					
						
							|  |  |  | 		// cleaning exif data using a native Go library. | 
					
						
							|  |  |  | 		log.Debug(ctx, "cleaning with exif-terminator") | 
					
						
							|  |  |  | 		err := terminateExif(outpath, filepath, ext) | 
					
						
							| 
									
										
										
										
											2024-08-08 17:12:13 +00:00
										 |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			// No problem. | 
					
						
							|  |  |  | 			break | 
					
						
							| 
									
										
										
										
											2024-08-02 11:46:41 +00:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-08-08 17:12:13 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		log.Warnf(ctx, "error cleaning with exif-terminator, falling back to ffmpeg: %v", err) | 
					
						
							|  |  |  | 		fallthrough | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-02 11:46:41 +00:00
										 |  |  | 	default: | 
					
						
							|  |  |  | 		// For all other types, best-effort clean with ffmpeg. | 
					
						
							|  |  |  | 		log.Debug(ctx, "cleaning with ffmpeg -map_metadata -1") | 
					
						
							|  |  |  | 		err := ffmpegClearMetadata(ctx, outpath, filepath) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Move the new output file path to original location. | 
					
						
							|  |  |  | 	if err := os.Rename(outpath, filepath); err != nil { | 
					
						
							|  |  |  | 		return gtserror.Newf("error renaming %s -> %s: %w", outpath, filepath, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // terminateExif cleans exif data from file at input path, into file | 
					
						
							|  |  |  | // at output path, exusing given file extension to determine cleaning. | 
					
						
							|  |  |  | func terminateExif(outpath, inpath string, ext string) error { | 
					
						
							|  |  |  | 	// Open input file at given path. | 
					
						
							|  |  |  | 	inFile, err := os.Open(inpath) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return gtserror.Newf("error opening input file %s: %w", inpath, err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-08-02 14:11:24 +00:00
										 |  |  | 	defer inFile.Close() | 
					
						
							| 
									
										
										
										
											2024-08-02 11:46:41 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Open output file at given path. | 
					
						
							|  |  |  | 	outFile, err := os.Create(outpath) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return gtserror.Newf("error opening output file %s: %w", outpath, err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-08-02 14:11:24 +00:00
										 |  |  | 	defer outFile.Close() | 
					
						
							| 
									
										
										
										
											2024-08-02 11:46:41 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Terminate EXIF data from 'inFile' -> 'outFile'. | 
					
						
							|  |  |  | 	err = terminator.TerminateInto(outFile, inFile, ext) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return gtserror.Newf("error terminating exif data: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } |