Use shared file publication in video processing
This commit is contained in:
1 parent
3ed4b73ebf
commit
6355bd1d73
2 files changed
+16
-155
No files matched your search
@@ -15,6 +15,8 @@ libuasync_a_SOURCES = \
|
||||
socket_compat.h \
|
||||
platform_compat.c \
|
||||
platform_compat.h \
|
||||
file_io.c \
|
||||
file_io.h \
|
||||
getmyip.c \
|
||||
getmyip.h \
|
||||
swm_min.c \
|
||||
|
||||
+14
-155
@@ -5,6 +5,7 @@
|
||||
#include "../../lib/mem.h"
|
||||
#include "../../lib/u_async.h"
|
||||
#include "../../lib/platform_compat.h"
|
||||
#include "../../lib/file_io.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -232,148 +233,6 @@ int video_needs_transcode(const struct video_info* info) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct vc_file {
|
||||
char temporary[VIDEO_PATH_MAX + 64];
|
||||
FILE* file;
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
static int vc_wide(const char* path, wchar_t* wide) {
|
||||
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, -1, wide, VIDEO_PATH_MAX + 64)) return 0;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video invalid UTF-8 path=%s winerr=%lu", path, GetLastError());
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Проверить обычные файлы и их идентичность до записи; повторить перед публикацией. */
|
||||
static int vc_paths(const char* source, const char* destination) {
|
||||
if (!source || !destination || !*source || !*destination ||
|
||||
strlen(source) >= VIDEO_PATH_MAX || strlen(destination) >= VIDEO_PATH_MAX) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video invalid/oversized source or destination");
|
||||
return -1;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
wchar_t from[VIDEO_PATH_MAX + 64], to[VIDEO_PATH_MAX + 64];
|
||||
if (vc_wide(source, from) || vc_wide(destination, to)) return -1;
|
||||
HANDLE input = CreateFileW(from, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
BY_HANDLE_FILE_INFORMATION a, b;
|
||||
int valid = input != INVALID_HANDLE_VALUE && GetFileInformationByHandle(input, &a) &&
|
||||
GetFileType(input) == FILE_TYPE_DISK && !(a.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
|
||||
if (input != INVALID_HANDLE_VALUE) CloseHandle(input);
|
||||
if (!valid) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video invalid source=%s winerr=%lu", source, GetLastError()); return -1; }
|
||||
DWORD attributes = GetFileAttributesW(to);
|
||||
if (attributes == INVALID_FILE_ATTRIBUTES) {
|
||||
DWORD error = GetLastError();
|
||||
if (error == ERROR_FILE_NOT_FOUND) return 0;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video destination inspection failed path=%s winerr=%lu", destination, error);
|
||||
return -1;
|
||||
}
|
||||
if (attributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video destination is directory/reparse point path=%s", destination); return -1;
|
||||
}
|
||||
HANDLE output = CreateFileW(to, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
valid = output != INVALID_HANDLE_VALUE && GetFileInformationByHandle(output, &b) && GetFileType(output) == FILE_TYPE_DISK;
|
||||
if (output != INVALID_HANDLE_VALUE) CloseHandle(output);
|
||||
if (!valid || (a.dwVolumeSerialNumber == b.dwVolumeSerialNumber &&
|
||||
a.nFileIndexHigh == b.nFileIndexHigh && a.nFileIndexLow == b.nFileIndexLow)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video destination aliases source or cannot be inspected path=%s", destination); return -1;
|
||||
}
|
||||
#else
|
||||
struct stat input, output;
|
||||
if (stat(source, &input) || !S_ISREG(input.st_mode)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video source is not readable regular file path=%s errno=%d", source, errno); return -1;
|
||||
}
|
||||
if (lstat(destination, &output)) {
|
||||
if (errno == ENOENT) return 0;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video destination stat failed path=%s errno=%d", destination, errno); return -1;
|
||||
}
|
||||
if (!S_ISREG(output.st_mode) || (input.st_dev == output.st_dev && input.st_ino == output.st_ino)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video destination is not distinct regular file source=%s destination=%s", source, destination);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Создать уникальный соседний файл эксклюзивно; FILE остаётся у задачи до finish/discard. */
|
||||
static int vc_file_open(struct vc_file* output, const char* destination) {
|
||||
for (int attempt = 0; attempt < 8; attempt++) {
|
||||
uint8_t random[16]; char suffix[33];
|
||||
if (random_bytes(random, sizeof(random))) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video temp randomness failed"); return -1; }
|
||||
for (unsigned i = 0; i < sizeof(random); i++) snprintf(suffix + i * 2, 3, "%02x", random[i]);
|
||||
snprintf(output->temporary, sizeof(output->temporary), "%s.tmp.%s", destination, suffix);
|
||||
#ifdef _WIN32
|
||||
wchar_t wide[VIDEO_PATH_MAX + 64];
|
||||
if (vc_wide(output->temporary, wide)) { output->temporary[0] = 0; return -1; }
|
||||
int fd = _wopen(wide, _O_CREAT | _O_EXCL | _O_RDWR | _O_BINARY, _S_IREAD | _S_IWRITE);
|
||||
#else
|
||||
int fd = open(output->temporary, O_CREAT | O_EXCL | O_RDWR, 0600);
|
||||
#endif
|
||||
if (fd < 0) {
|
||||
output->temporary[0] = 0;
|
||||
if (errno == EEXIST) continue;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video temporary creation failed destination=%s errno=%d", destination, errno);
|
||||
return -1;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
output->file = _fdopen(fd, "wb+");
|
||||
if (!output->file) _close(fd);
|
||||
#else
|
||||
output->file = fdopen(fd, "wb+");
|
||||
if (!output->file) close(fd);
|
||||
#endif
|
||||
if (!output->file) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video fdopen failed errno=%d", errno); return -1; }
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "video temporary opened path=%s", output->temporary);
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video temporary collision limit destination=%s", destination);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int vc_file_finish(struct vc_file* output) {
|
||||
int error = fflush(output->file) ? errno : 0;
|
||||
if (fclose(output->file) && !error) error = errno;
|
||||
output->file = NULL;
|
||||
if (error) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video flush/close failed path=%s errno=%d", output->temporary, error); return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vc_file_discard(struct vc_file* output) {
|
||||
if (output->file) {
|
||||
if (fclose(output->file)) DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "video cleanup close failed errno=%d", errno);
|
||||
output->file = NULL;
|
||||
}
|
||||
if (!output->temporary[0]) return;
|
||||
#ifdef _WIN32
|
||||
wchar_t wide[VIDEO_PATH_MAX + 64];
|
||||
int error = vc_wide(output->temporary, wide) ? -1 : _wremove(wide);
|
||||
#else
|
||||
int error = remove(output->temporary);
|
||||
#endif
|
||||
if (error && errno != ENOENT) DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "video temporary cleanup failed path=%s errno=%d", output->temporary, errno);
|
||||
output->temporary[0] = 0;
|
||||
}
|
||||
|
||||
/* Последняя способная отказать операция: после rename результат уже опубликован. */
|
||||
static int vc_file_publish(struct vc_file* output, const char* source, const char* destination) {
|
||||
if (vc_paths(source, destination)) return -1;
|
||||
#ifdef _WIN32
|
||||
wchar_t from[VIDEO_PATH_MAX + 64], to[VIDEO_PATH_MAX + 64];
|
||||
if (vc_wide(output->temporary, from) || vc_wide(destination, to)) return -1;
|
||||
if (!MoveFileExW(from, to, MOVEFILE_REPLACE_EXISTING)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video publish failed destination=%s winerr=%lu", destination, GetLastError()); return -1;
|
||||
}
|
||||
#else
|
||||
if (rename(output->temporary, destination)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video publish failed destination=%s errno=%d", destination, errno); return -1;
|
||||
}
|
||||
#endif
|
||||
output->temporary[0] = 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "video published destination=%s", destination);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct vc_stream {
|
||||
int index;
|
||||
@@ -391,7 +250,7 @@ struct vc_stream {
|
||||
|
||||
struct vc_ctx {
|
||||
char source[VIDEO_PATH_MAX], destination[VIDEO_PATH_MAX];
|
||||
struct vc_file file;
|
||||
struct file_io_temp file;
|
||||
void (*done)(void*, int, const struct video_info*);
|
||||
void* arg;
|
||||
int error, remux;
|
||||
@@ -409,7 +268,7 @@ static int vc_io_write(void* opaque, const uint8_t* bytes, int length) {
|
||||
#else
|
||||
static int vc_io_write(void* opaque, uint8_t* bytes, int length) {
|
||||
#endif
|
||||
struct vc_file* file = opaque;
|
||||
struct file_io_temp* file = opaque;
|
||||
if (fwrite(bytes, 1, length, file->file) == (size_t)length) return length;
|
||||
int error = errno ? errno : EIO;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video MP4 write failed path=%s requested=%d errno=%d", file->temporary, length, error);
|
||||
@@ -417,7 +276,7 @@ static int vc_io_write(void* opaque, uint8_t* bytes, int length) {
|
||||
}
|
||||
|
||||
static int64_t vc_io_seek(void* opaque, int64_t offset, int whence) {
|
||||
struct vc_file* file = opaque;
|
||||
struct file_io_temp* file = opaque;
|
||||
if (whence == AVSEEK_SIZE) {
|
||||
#ifdef _WIN32
|
||||
struct _stat64 info;
|
||||
@@ -459,7 +318,7 @@ static int vc_output_close(struct vc_ctx* ctx) {
|
||||
avio_context_free(&ctx->output->pb);
|
||||
if (error < 0) DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video MP4 buffered output failed error=%s", vc_errstr(error));
|
||||
}
|
||||
if (ctx->file.file && vc_file_finish(&ctx->file)) error = -1;
|
||||
if (ctx->file.file && file_io_temp_finish(&ctx->file)) error = -1;
|
||||
return error < 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
@@ -854,7 +713,7 @@ static int vc_run(struct vc_ctx* ctx) {
|
||||
static void vc_thread_entry(void* raw) {
|
||||
struct vc_ctx* ctx = raw;
|
||||
video_ffmpeg_init();
|
||||
if (vc_paths(ctx->source, ctx->destination) || vc_file_open(&ctx->file, ctx->destination)) { ctx->error = -1; return; }
|
||||
if (file_io_paths(ctx->source, ctx->destination) || file_io_temp_open(&ctx->file, ctx->destination)) { ctx->error = -1; return; }
|
||||
struct stat info;
|
||||
if (!stat(ctx->source, &info)) ctx->source_size = info.st_size;
|
||||
int error = vc_run(ctx);
|
||||
@@ -873,8 +732,8 @@ static void vc_thread_entry(void* raw) {
|
||||
static void vc_done_trampoline(void* raw, int error) {
|
||||
struct vc_ctx* ctx = raw;
|
||||
if (!error) error = ctx->error;
|
||||
if (!error && vc_file_publish(&ctx->file, ctx->source, ctx->destination)) error = -1;
|
||||
vc_file_discard(&ctx->file);
|
||||
if (!error && file_io_temp_publish(&ctx->file, ctx->source, ctx->destination)) error = -1;
|
||||
file_io_temp_discard(&ctx->file);
|
||||
vc_progress_set(-1);
|
||||
vc_active_set(0);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "video completion destination=%s error=%d", ctx->destination, error);
|
||||
@@ -929,11 +788,11 @@ static int vc_first_frame(AVFormatContext* format, AVCodecContext* decoder, int
|
||||
}
|
||||
|
||||
int video_extract_thumbnail(const char* src, const char* dst, int max_w) {
|
||||
if (vc_paths(src, dst)) return -1;
|
||||
if (file_io_paths(src, dst)) return -1;
|
||||
if (max_w <= 0) max_w = 320;
|
||||
if (max_w < 2) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video thumbnail width must be >=2"); return -1; }
|
||||
video_ffmpeg_init();
|
||||
struct vc_file output = {0};
|
||||
struct file_io_temp output = {0};
|
||||
AVFormatContext* format = NULL;
|
||||
AVCodecContext* decoder = NULL; AVCodecContext* encoder = NULL;
|
||||
AVFrame* input = NULL; AVFrame* frame = NULL; AVPacket* packet = NULL;
|
||||
@@ -983,17 +842,17 @@ int video_extract_thumbnail(const char* src, const char* dst, int max_w) {
|
||||
}
|
||||
if (error < 0) goto done;
|
||||
phase = "write JPEG";
|
||||
if (vc_file_open(&output, dst)) { error = AVERROR(EIO); goto done; }
|
||||
if (file_io_temp_open(&output, dst)) { error = AVERROR(EIO); goto done; }
|
||||
if (fwrite(packet->data, 1, packet->size, output.file) != (size_t)packet->size) {
|
||||
error = AVERROR(errno ? errno : EIO); goto done;
|
||||
}
|
||||
if (vc_file_finish(&output)) { error = AVERROR(EIO); goto done; }
|
||||
if (file_io_temp_finish(&output)) { error = AVERROR(EIO); goto done; }
|
||||
phase = "publish JPEG";
|
||||
error = vc_file_publish(&output, src, dst) ? AVERROR(EIO) : 0;
|
||||
error = file_io_temp_publish(&output, src, dst) ? AVERROR(EIO) : 0;
|
||||
done:
|
||||
if (error < 0) DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video thumbnail failed phase=%s source=%s destination=%s error=%s",
|
||||
phase, src, dst, vc_errstr(error));
|
||||
vc_file_discard(&output);
|
||||
file_io_temp_discard(&output);
|
||||
sws_freeContext(scale); av_frame_free(&input); av_frame_free(&frame); av_packet_free(&packet);
|
||||
avcodec_free_context(&decoder); avcodec_free_context(&encoder); avformat_close_input(&format);
|
||||
return error < 0 ? -1 : 0;
|
||||
|
||||
Reference in new issue
Block a user