forked from theluyuan/avs2bdnxml
1374 lines
37 KiB
C
1374 lines
37 KiB
C
/*----------------------------------------------------------------------------
|
||
* avs2bdnxml - Generates BluRay subtitle stuff from RGBA AviSynth scripts
|
||
* Copyright (C) 2008-2013 Arne Bochem <avs2bdnxml at ps-auxw de>
|
||
*
|
||
* This program is free software: you can redistribute it and/or modify
|
||
* it under the terms of the GNU 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 General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License
|
||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
*----------------------------------------------------------------------------
|
||
* Version 2.09
|
||
* - Added parameter -F to mark all subtitles forced
|
||
*
|
||
* Version 2.08
|
||
* - Fix PNG filename references when used with timecode offsets
|
||
*
|
||
* Version 2.07
|
||
* - Fix two bugs in timecode parsing function
|
||
* - Move most inline assembly to yasm
|
||
*
|
||
* Version 2.06
|
||
* - Add option to add an offset to output timecodes
|
||
* - Change return value when no events were detected to 0
|
||
* - Add option to allow writing of empty output files
|
||
* - Add option to enforce even Y coordinates (may help avoid problems with
|
||
* interlaced video in DVD related use cases)
|
||
* - Fix SSE2 with more recent GCC versions
|
||
* - Add option for stricter checks on buffer/palette limits for SUPs
|
||
* - Fix bug where LastEventOutTC could be one frame early
|
||
* - Add additional checks to commandline argument parsing
|
||
* - PNG files are now always placed in the same directory as the XML file
|
||
*
|
||
* Version 2.05
|
||
* - Fix crash when using XML and SUP output
|
||
*
|
||
* Version 2.04
|
||
* - Minor fixes
|
||
* - Code cleanup regarding linked lists
|
||
* - Add new minimal window finding code
|
||
* - Fix PGS output more, especially WDS handling
|
||
* - Fix old bug in RLE encoder
|
||
*
|
||
* Version 2.03
|
||
* - Output some statistics about previous epoch in pgsparse in end packet
|
||
* - Fix unnecessary epoch splits due to bad buffer calculation and more.
|
||
* - Some small fixes
|
||
*
|
||
* Version 2.02
|
||
* - It is now possible to use -o twice, with different output formats
|
||
* - Corrections to SUP timestamp calculation
|
||
* - PGS decode buffer should contain palettized image data, not RGBA.
|
||
* Buffer usage calculation fixed.
|
||
*
|
||
* Version 2.01
|
||
* - Restrict number of composition objects within an epoch to 64
|
||
*
|
||
* Version 2.00
|
||
* - Options --seek and --count were added, to allow for partial processing
|
||
* of input video
|
||
* - For debug purposes, a stand-alone SUP file parser was added in debug/
|
||
* - Experimental SUP output support
|
||
*
|
||
* Version 1.13
|
||
* - By default, images are no longer split into to composition objects,
|
||
* if the result would look ugly. For example, descenders should no
|
||
* longer get put into their own image. Reverting back to the old
|
||
* behaviour is possible using the --ugly option.
|
||
* - Ensure minimum size of 8x8 for composition objects
|
||
*
|
||
* Version 1.12
|
||
* - Fix error in example
|
||
* - Fix double free, which could result in broken images when image
|
||
* splitting and palletized output were enabled.
|
||
*
|
||
* Version 1.11
|
||
* - Add option to not split lines, if the rest isn't a minimum
|
||
* of 3 (default) frames long
|
||
* - Images are now autocropped by default
|
||
* - Images are now converted to 8bit palette by default
|
||
* - Misc bugfixes that should've been fixed before
|
||
*
|
||
* Version 1.10b
|
||
* - Splitting of images is now disabled by default
|
||
*
|
||
* Version 1.10
|
||
* - Add option to split events longer than a given number of frames, to
|
||
* allow better seeking
|
||
* - Change image filename format to XXXXXXXX_X.png
|
||
* - Add option to optimize for Presentation Graphics buffer usage, by
|
||
* auto-cropping images, and splitting them into two separate ones to
|
||
* minimize surface area, when necessary (enabled by default)
|
||
* - Use getopts for commandline argument parsing
|
||
* - Add Makefile
|
||
*
|
||
* Version 1.9
|
||
* - Correct calculation of timecodes. Integer only, now.
|
||
* - Remove support for drop timecodes. If anybody really needs them, and
|
||
* has a way to check calculated timecodes are correct, please tell me.
|
||
*
|
||
* Version 1.8
|
||
* - Fix crash with certain frame sizes (unaligned buffer used with SSE2)
|
||
*
|
||
* Version 1.7
|
||
* - Terminate using exit/return instead of abort (no more "crashes")
|
||
*
|
||
* Version 1.6
|
||
* - Zero transparent pixels to guard against invisible color information
|
||
* (Slight slowdown, but more robustness)
|
||
* - Add SSE2 runtime detection
|
||
* - Correctly set DropFrame attribute
|
||
*
|
||
* Version 1.5
|
||
* - Validate framerates
|
||
* - Make non-drop timecodes the default for 23.976, 29.97 and 59.94
|
||
* - Fix bad timecode calculation due to float math optimization
|
||
* - Add integer SSE2 optimization (but most time is spent in AviSynth)
|
||
* - Don't uselessly swap R and B channels
|
||
*
|
||
* Version 1.4
|
||
* - Add 30 to framerate list
|
||
* - Enhance timecode precision
|
||
*
|
||
* Version 1.3
|
||
* - Read frames correctly in avs2bdnxml. No more FlipVertical() necessary.
|
||
*
|
||
* Version 1.2
|
||
* - Fixed bug, where only the bottom part of the image was checked for
|
||
* changes
|
||
*
|
||
* Version 1.1
|
||
* - Fixed crash bug with videos shorter than 1000 frames.
|
||
* - Convert from BGR to RGB, colors in PNGs should be correct now.
|
||
*
|
||
* Version 1.0
|
||
* - Initial release. Seems to work.
|
||
*----------------------------------------------------------------------------
|
||
* Thanks to:
|
||
* - hamletiii on Doom9 for a steady flow of feedback.
|
||
* - Daiz in #x264 for inspiration, testing, and bug reports.
|
||
* - Loren Merritt and Laurent Aimar for writing x264's AVS reader code.
|
||
*----------------------------------------------------------------------------*/
|
||
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <limits.h>
|
||
#include <time.h>
|
||
#include <png.h>
|
||
#include <getopt.h>
|
||
#include <assert.h>
|
||
#include "auto_split.h"
|
||
#include "palletize.h"
|
||
#include "sup.h"
|
||
#include "ass.h"
|
||
#include "abstract_lists.h"
|
||
#include <emmintrin.h> // 包含SSE指令集的头文件
|
||
|
||
/* AVIS input code taken from muxers.c from the x264 project (GPLv2 or later).
|
||
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
|
||
* Loren Merritt <lorenm@u.washington.edu>
|
||
* Slightly modified, so if you find breakage, it's probably mine. :)
|
||
*/
|
||
#ifndef LINUX
|
||
#include <windows.h>
|
||
#include <vfw.h>
|
||
#endif
|
||
|
||
#ifndef LINUX
|
||
static int64_t gcd( int64_t a, int64_t b )
|
||
{
|
||
while (1)
|
||
{
|
||
int64_t c = a % b;
|
||
if( !c )
|
||
return b;
|
||
a = b;
|
||
b = c;
|
||
}
|
||
}
|
||
#endif
|
||
|
||
typedef struct {
|
||
#if !defined(LINUX)
|
||
PAVISTREAM p_avi;
|
||
#else
|
||
int fps_den;
|
||
int fps_num;
|
||
int frames;
|
||
FILE *fh;
|
||
#endif
|
||
int width, height;
|
||
} avis_input_t;
|
||
|
||
typedef struct {
|
||
int i_width;
|
||
int i_height;
|
||
int i_fps_den;
|
||
int i_fps_num;
|
||
} stream_info_t;
|
||
|
||
int open_file_avis( char *psz_filename, avis_input_t **p_handle, stream_info_t *p_param )
|
||
{
|
||
avis_input_t *h = malloc(sizeof(avis_input_t));
|
||
#if !defined(LINUX)
|
||
AVISTREAMINFO info;
|
||
int i;
|
||
|
||
*p_handle = h;
|
||
|
||
AVIFileInit();
|
||
if( AVIStreamOpenFromFile( &h->p_avi, psz_filename, streamtypeVIDEO, 0, OF_READ, NULL ) )
|
||
{
|
||
AVIFileExit();
|
||
return -1;
|
||
}
|
||
|
||
if( AVIStreamInfo(h->p_avi, &info, sizeof(AVISTREAMINFO)) )
|
||
{
|
||
AVIStreamRelease(h->p_avi);
|
||
AVIFileExit();
|
||
return -1;
|
||
}
|
||
|
||
/* Check input format */
|
||
if (info.fccHandler != MAKEFOURCC('D', 'I', 'B', ' '))
|
||
{
|
||
fprintf( stderr, "avis [error]: unsupported input format (%c%c%c%c)\n",
|
||
(char)(info.fccHandler & 0xff), (char)((info.fccHandler >> 8) & 0xff),
|
||
(char)((info.fccHandler >> 16) & 0xff), (char)((info.fccHandler >> 24)) );
|
||
|
||
AVIStreamRelease(h->p_avi);
|
||
AVIFileExit();
|
||
|
||
return -1;
|
||
}
|
||
|
||
h->width =
|
||
p_param->i_width = info.rcFrame.right - info.rcFrame.left;
|
||
h->height =
|
||
p_param->i_height = info.rcFrame.bottom - info.rcFrame.top;
|
||
i = gcd(info.dwRate, info.dwScale);
|
||
p_param->i_fps_den = info.dwScale / i;
|
||
p_param->i_fps_num = info.dwRate / i;
|
||
|
||
fprintf( stderr, "avis [info]: %dx%d @ %.2f fps (%d frames)\n",
|
||
p_param->i_width, p_param->i_height,
|
||
(double)p_param->i_fps_num / (double)p_param->i_fps_den,
|
||
(int)info.dwLength );
|
||
|
||
return 0;
|
||
#else
|
||
*p_handle = h;
|
||
p_param->i_width = 1920;
|
||
p_param->i_height = 1080;
|
||
p_param->i_fps_den = 30000;
|
||
p_param->i_fps_num = 1001;
|
||
h->width = p_param->i_width;
|
||
h->height = p_param->i_height;
|
||
h->fps_den = p_param->i_fps_den;
|
||
h->fps_num = p_param->i_fps_num;
|
||
h->fh = fopen(psz_filename, "r");
|
||
h->frames = 15000;
|
||
return 0;
|
||
#endif
|
||
}
|
||
|
||
int get_frame_total_avis( avis_input_t *handle )
|
||
{
|
||
#if !defined(LINUX)
|
||
avis_input_t *h = handle;
|
||
AVISTREAMINFO info;
|
||
|
||
if( AVIStreamInfo(h->p_avi, &info, sizeof(AVISTREAMINFO)) )
|
||
return -1;
|
||
|
||
return info.dwLength;
|
||
#else
|
||
return handle->frames;
|
||
#endif
|
||
}
|
||
|
||
int read_frame_avis( char *p_pic, avis_input_t *handle, int i_frame )
|
||
{
|
||
#if !defined(LINUX)
|
||
avis_input_t *h = handle;
|
||
|
||
if( AVIStreamRead(h->p_avi, i_frame, 1, p_pic, h->width * h->height * 4, NULL, NULL ) )
|
||
return -1;
|
||
|
||
return 0;
|
||
#else
|
||
fread(p_pic, 4, handle->width * handle->height, handle->fh);
|
||
return 0;
|
||
#endif
|
||
}
|
||
|
||
int close_file_avis( avis_input_t *handle )
|
||
{
|
||
#if !defined(LINUX)
|
||
avis_input_t *h = handle;
|
||
AVIStreamRelease(h->p_avi);
|
||
AVIFileExit();
|
||
free(h);
|
||
return 0;
|
||
#else
|
||
fclose((FILE *)handle);
|
||
return 0;
|
||
#endif
|
||
}
|
||
|
||
/* AVIS code ends here */
|
||
|
||
/* Main avs2bdnxml code starts here, too */
|
||
|
||
void get_dir_path(char *filename, char *dir_path)
|
||
{
|
||
char abs_path[MAX_PATH + 1] = {0};
|
||
char drive[3] = {0};
|
||
char dir[MAX_PATH + 1] = {0};
|
||
|
||
/* Get absolute path of output XML file */
|
||
if (_fullpath(abs_path, filename, MAX_PATH) == NULL)
|
||
{
|
||
printf( "Cannot determine absolute path for: %s\r\n", filename);
|
||
exit(1);
|
||
}
|
||
|
||
/* Split absolute path into components */
|
||
_splitpath(abs_path, drive, dir, NULL, NULL);
|
||
strncpy(dir_path, drive, 2);
|
||
strncat(dir_path, dir, MAX_PATH - 2);
|
||
|
||
if (strlen(dir_path) > MAX_PATH - 16)
|
||
{
|
||
printf( "Path for PNG files too long.\r\n");
|
||
exit(1);
|
||
}
|
||
}
|
||
|
||
void write_png(char *dir, int file_id, uint8_t *image, int w, int h, int graphic, uint32_t *pal, crop_t c)
|
||
{
|
||
FILE *fh;
|
||
png_structp png_ptr;
|
||
png_infop info_ptr;
|
||
png_bytep *row_pointers;
|
||
png_colorp palette = NULL;
|
||
png_bytep trans = NULL;
|
||
char tmp[16] = {0};
|
||
char filename[MAX_PATH + 1] = {0};
|
||
char *col;
|
||
int step = pal == NULL ? 4 : 1;
|
||
int colors = 0;
|
||
int i;
|
||
|
||
snprintf(tmp, 15, "%08d_%d.png", file_id, graphic);
|
||
strncpy(filename, dir, MAX_PATH);
|
||
strncat(filename, tmp, 15);
|
||
|
||
if ((fh = fopen(filename, "wb")) == NULL)
|
||
{
|
||
perror("Cannot open PNG file for writing");
|
||
exit(1);
|
||
}
|
||
|
||
/* Initialize png struct */
|
||
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||
if (png_ptr == NULL)
|
||
{
|
||
printf( "Cannot create png_ptr.\r\n");
|
||
exit(1);
|
||
}
|
||
|
||
/* Initialize info struct */
|
||
info_ptr = png_create_info_struct(png_ptr);
|
||
if (info_ptr == NULL)
|
||
{
|
||
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
|
||
printf( "Cannot create info_ptr.\r\n");
|
||
exit(1);
|
||
}
|
||
|
||
/* Set long jump stuff (weird..?) */
|
||
if (setjmp(png_jmpbuf(png_ptr)))
|
||
{
|
||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||
fclose(fh);
|
||
printf( "Error while writing PNG file: %s\r\n", filename);
|
||
exit(1);
|
||
}
|
||
|
||
/* Initialize IO */
|
||
png_init_io(png_ptr, fh);
|
||
|
||
/* Set file info */
|
||
if (pal == NULL)
|
||
png_set_IHDR(png_ptr, info_ptr, c.w, c.h, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||
else
|
||
{
|
||
png_set_IHDR(png_ptr, info_ptr, c.w, c.h, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||
palette = calloc(256, sizeof(png_color));
|
||
trans = calloc(256, sizeof(png_byte));
|
||
colors = 1;
|
||
for (i = 1; i < 256 && pal[i]; i++)
|
||
{
|
||
col = (char *)&(pal[i]);
|
||
palette[i].red = col[0];
|
||
palette[i].green = col[1];
|
||
palette[i].blue = col[2];
|
||
trans[i] = col[3];
|
||
colors++;
|
||
}
|
||
png_set_PLTE(png_ptr, info_ptr, palette, colors);
|
||
png_set_tRNS(png_ptr, info_ptr, trans, colors, NULL);
|
||
}
|
||
|
||
/* Allocate row pointer memory */
|
||
row_pointers = calloc(c.h, sizeof(png_bytep));
|
||
|
||
/* Set row pointers */
|
||
image = image + step * (c.x + w * c.y);
|
||
for (i = 0; i < c.h; i++)
|
||
{
|
||
row_pointers[i] = image + i * w * step;
|
||
}
|
||
png_set_rows(png_ptr, info_ptr, row_pointers);
|
||
|
||
/* Set compression */
|
||
png_set_filter(png_ptr, 0, PNG_FILTER_VALUE_SUB);
|
||
png_set_compression_level(png_ptr, 5);
|
||
|
||
/* Write image */
|
||
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
|
||
|
||
/* Free memory */
|
||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||
free(row_pointers);
|
||
if (palette != NULL)
|
||
free(palette);
|
||
if (trans != NULL)
|
||
free(trans);
|
||
|
||
/* Close file handle */
|
||
fclose(fh);
|
||
}
|
||
|
||
extern int asm_is_identical_sse2 (stream_info_t *s_info, char *img, char *img_old);
|
||
extern int asm_is_empty_sse2 (stream_info_t *s_info, char *img);
|
||
extern void asm_zero_transparent_sse2 (stream_info_t *s_info, char volatile *img);
|
||
extern void asm_swap_rb_sse2 (stream_info_t *s_info, char volatile *img, char volatile *out);
|
||
|
||
int is_identical_c (stream_info_t *s_info, char *img, char *img_old)
|
||
{
|
||
uint32_t *max = (uint32_t *)(img + s_info->i_width * s_info->i_height * 4);
|
||
uint32_t *im = (uint32_t *)img;
|
||
uint32_t *im_old = (uint32_t *)img_old;
|
||
|
||
while (im < max)
|
||
{
|
||
if (!((char *)im)[3])
|
||
*im = 0;
|
||
if (*(im++) ^ *(im_old++))
|
||
return 0;
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
int is_empty_c (stream_info_t *s_info, char *img)
|
||
{
|
||
char *max = img + s_info->i_width * s_info->i_height * 4;
|
||
char *im = img;
|
||
|
||
while (im < max)
|
||
{
|
||
if (im[3])
|
||
return 0;
|
||
im += 4;
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
void zero_transparent_c (stream_info_t *s_info, char *img)
|
||
{
|
||
char *max = img + s_info->i_width * s_info->i_height * 4;
|
||
char *im = img;
|
||
|
||
while (im < max)
|
||
{
|
||
if (!im[3])
|
||
*(uint32_t *)img = 0;
|
||
im += 4;
|
||
}
|
||
}
|
||
|
||
void swap_rb_c (stream_info_t *s_info, char *img, char *out)
|
||
{
|
||
char *max = img + s_info->i_width * s_info->i_height * 4;
|
||
|
||
while (img < max)
|
||
{
|
||
out[0] = img[2];
|
||
out[1] = img[1];
|
||
out[2] = img[0];
|
||
out[3] = img[3];
|
||
img += 4;
|
||
out += 4;
|
||
}
|
||
}
|
||
|
||
int detect_sse2 ()
|
||
{
|
||
static int detection = -1;
|
||
unsigned int func = 0x00000001;
|
||
unsigned int eax, ebx, ecx, edx;
|
||
|
||
if (detection != -1)
|
||
return detection;
|
||
|
||
asm volatile
|
||
(
|
||
"cpuid\n"
|
||
: "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx)
|
||
: "a" (func)
|
||
);
|
||
|
||
/* SSE2: edx & 0x04000000
|
||
* SSSE3: ecx & 0x00000200
|
||
*/
|
||
detection = (edx & 0x04000000) ? 1 : 0;
|
||
|
||
if (detection)
|
||
printf( "CPU: Using SSE2 optimized functions.\r\n");
|
||
else
|
||
printf( "CPU: Using pure C functions.\r\n");
|
||
|
||
return detection;
|
||
}
|
||
|
||
int is_identical (stream_info_t *s_info, char *img, char *img_old)
|
||
{
|
||
|
||
// 按128位加载两个图像数据
|
||
__m128i* ptr1 = (__m128i*)img;
|
||
__m128i* ptr2 = (__m128i*)img_old;
|
||
|
||
// 计算比较的次数(以128位为单位)
|
||
int num_iterations = size / 16;
|
||
|
||
// 逐一比较图像数据,使用SSE指令进行并行比较
|
||
for (int i = 0; i < num_iterations; ++i) {
|
||
__m128i xmm1 = _mm_load_si128(ptr1 + i); // 加载128位数据
|
||
__m128i xmm2 = _mm_load_si128(ptr2 + i); // 加载128位数据
|
||
|
||
// 比较两个128位数据是否相等
|
||
__m128i cmp_result = _mm_cmpeq_epi8(xmm1, xmm2);
|
||
|
||
// 判断比较结果是否全为1
|
||
int result_mask = _mm_movemask_epi8(cmp_result);
|
||
|
||
// 如果存在不相等的元素,返回0
|
||
if (result_mask != 0xFFFF) {
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
// 如果所有元素都相等,返回1
|
||
return 1;
|
||
}
|
||
|
||
int is_empty (stream_info_t *s_info, char *img)
|
||
{
|
||
if (detect_sse2())
|
||
return asm_is_empty_sse2(s_info, img);
|
||
else
|
||
return is_empty_c(s_info, img);
|
||
}
|
||
|
||
void zero_transparent (stream_info_t *s_info, char *img)
|
||
{
|
||
if (detect_sse2())
|
||
return asm_zero_transparent_sse2(s_info, img);
|
||
else
|
||
return zero_transparent_c(s_info, img);
|
||
}
|
||
|
||
void swap_rb (stream_info_t *s_info, char *img, char *out)
|
||
{
|
||
if (detect_sse2())
|
||
return asm_swap_rb_sse2(s_info, img, out);
|
||
else
|
||
return swap_rb_c(s_info, img, out);
|
||
}
|
||
|
||
/* SMPTE non-drop time code */
|
||
void mk_timecode (int frame, int fps, char *buf) /* buf must have length 12 (incl. trailing \0) */
|
||
{
|
||
int frames, s, m, h;
|
||
int tc = frame;
|
||
|
||
tc = frame;
|
||
frames = tc % fps;
|
||
tc /= fps;
|
||
s = tc % 60;
|
||
tc /= 60;
|
||
m = tc % 60;
|
||
tc /= 60;
|
||
h = tc;
|
||
|
||
if (h > 99)
|
||
{
|
||
printf( "Timecodes above 99:59:59:99 not supported: %u:%02u:%02u:%02u\r\n", h, m, s, frames);
|
||
exit(1);
|
||
}
|
||
|
||
if (snprintf(buf, 12, "%02d:%02d:%02d:%02d", h, m, s, frames) != 11)
|
||
{
|
||
printf( "Timecode lead to invalid format: %s\r\n", buf);
|
||
exit(1);
|
||
}
|
||
}
|
||
|
||
void print_usage ()
|
||
{
|
||
printf(
|
||
"avs2bdnxml 2.09\n\n"
|
||
"Usage: avs2bdnxml [options] -o output input\n\n"
|
||
"Input has to be an AviSynth script with RGBA as output colorspace\n\n"
|
||
" -o, --output <string> Output file in BDN XML format\n"
|
||
" For SUP/PGS output, use a .sup extension\n"
|
||
" -j, --seek <integer> Start processing at this frame, first is 0\n"
|
||
" -c, --count <integer> Number of input frames to process\n"
|
||
" -t, --trackname <string> Name of track, like: Undefined\n"
|
||
" -l, --language <string> Language code, like: und\n"
|
||
" -v, --video-format <string> Either of: 480i, 480p, 576i,\n"
|
||
" 720p, 1080i, 1080p\n"
|
||
" -f, --fps <float> Either of: 23.976, 24, 25, 29.97, 50, 59.94\n"
|
||
" -x, --x-offset <integer> X offset, for use with partial frames.\n"
|
||
" -y, --y-offset <integer> Y offset, for use with partial frames.\n"
|
||
" -d, --t-offset <string> Offset timecodes by this many frames or\n"
|
||
" given non-drop timecode (HH:MM:SS:FF).\n"
|
||
" -s, --split-at <integer> Split events longer than this, in frames.\n"
|
||
" Disabled when 0, which is the default.\n"
|
||
" -m, --min-split <integer> Minimum length of line segment after split.\n"
|
||
" -e, --even-y <integer> Enforce even Y coordinates. [on=1, off=0]\n"
|
||
" -a, --autocrop <integer> Automatically crop output. [on=1, off=0]\n"
|
||
" -p, --palette <integer> Output 8bit palette PNG. [on=1, off=0]\n"
|
||
" -n, --null-xml <integer> Allow output of empty XML files. [on=1, off=0]\n"
|
||
" -z, --stricter <integer> Stricter checks in the SUP writer. May lead to\n"
|
||
" less optimized buffer use, but might raise\n"
|
||
" compatibility. [on=1, off=0]\n"
|
||
" -u, --ugly <integer> Allow splitting images in ugly ways.\n"
|
||
" Might improve buffer problems, but is ugly.\n"
|
||
" [on=1, off=0]\n"
|
||
" -b, --buffer-opt <integer> Optimize PG buffer size by image\n"
|
||
" splitting. [on=1, off=0]\n"
|
||
" -F, --forced <integer> mark all subtitles as forced [on=1, off=0]\n\n"
|
||
"Example:\n"
|
||
" avs2bdnxml -t Undefined -l und -v 1080p -f 23.976 -a1 -p1 -b0 -m3 \\\n"
|
||
" -u0 -e0 -n0 -z0 -o output.xml input.avs\n"
|
||
" (Input and output are required settings. The rest are set to default.)\n"
|
||
);
|
||
}
|
||
|
||
extern char *strrchr(const char *s, int c);
|
||
int is_extension(char *filename, char *check_ext)
|
||
{
|
||
char *ext = strrchr(filename, '.');
|
||
|
||
if (ext == NULL)
|
||
return 0;
|
||
|
||
ext++;
|
||
if (!strcasecmp(ext, check_ext))
|
||
return 1;
|
||
|
||
return 0;
|
||
}
|
||
|
||
int parse_int(char *in, char *name, int *error)
|
||
{
|
||
char *end;
|
||
int r;
|
||
errno = 0;
|
||
if (error != NULL)
|
||
*error = 0;
|
||
r = strtol(in, &end, 10);
|
||
if (errno || end == in || end != in + strlen(in))
|
||
{
|
||
if (error != NULL)
|
||
*error = 1;
|
||
if (name != NULL)
|
||
{
|
||
printf( "Error: Failed to parse integer (%s): %s\r\n", name, in);
|
||
exit(1);
|
||
}
|
||
}
|
||
return r;
|
||
}
|
||
|
||
int parse_tc(char *in, int fps)
|
||
{
|
||
int r = 0;
|
||
int e;
|
||
int h, m, s, f;
|
||
|
||
/* Test for raw frame number. */
|
||
r = parse_int(in, NULL, &e);
|
||
if (!e)
|
||
return r;
|
||
|
||
if (strlen(in) != 2 * 4 + 3 || in[2] != ':' || in[5] != ':' || in[8] != ':')
|
||
{
|
||
printf( "Error: Invalid timecode offset. Expected FRAMENUMBER or HH:MM:SS:FF, but got: %s\r\n", in);
|
||
exit(1);
|
||
}
|
||
in[2] = 0;
|
||
in[5] = 0;
|
||
in[8] = 0;
|
||
h = parse_int(in, "t-offset hours", NULL);
|
||
m = parse_int(in + 3, "t-offset minutes", NULL);
|
||
s = parse_int(in + 6, "t-offset seconds", NULL);
|
||
f = parse_int(in + 9, "t-offset frames", NULL);
|
||
r = f;
|
||
r += s * fps;
|
||
fps *= 60;
|
||
r += m * fps;
|
||
fps *= 60;
|
||
r += h * fps;
|
||
return r;
|
||
}
|
||
|
||
typedef struct event_s
|
||
{
|
||
int image_number;
|
||
int start_frame;
|
||
int end_frame;
|
||
int graphics;
|
||
int forced;
|
||
crop_t c[2];
|
||
} event_t;
|
||
|
||
STATIC_LIST(event, event_t)
|
||
|
||
void add_event_xml_real (event_list_t *events, int image, int start, int end, int graphics, crop_t *crops, int forced)
|
||
{
|
||
event_t *new = calloc(1, sizeof(event_t));
|
||
new->image_number = image;
|
||
new->start_frame = start;
|
||
new->end_frame = end;
|
||
new->graphics = graphics;
|
||
new->c[0] = crops[0];
|
||
new->c[1] = crops[1];
|
||
new->forced = forced;
|
||
event_list_insert_after(events, new);
|
||
}
|
||
|
||
void add_event_xml (event_list_t *events, int split_at, int min_split, int start, int end, int graphics, crop_t *crops, int forced)
|
||
{
|
||
int image = start;
|
||
int d = end - start;
|
||
|
||
if (!split_at)
|
||
add_event_xml_real(events, image, start, end, graphics, crops, forced);
|
||
else
|
||
{
|
||
while (d >= split_at + min_split)
|
||
{
|
||
d -= split_at;
|
||
add_event_xml_real(events, image, start, start + split_at, graphics, crops, forced);
|
||
start += split_at;
|
||
}
|
||
if (d)
|
||
add_event_xml_real(events, image, start, start + d, graphics, crops, forced);
|
||
}
|
||
}
|
||
|
||
void write_sup_wrapper (sup_writer_t *sw, uint8_t *im, int num_crop, crop_t *crops, uint32_t *pal, int start, int end, int split_at, int min_split, int stricter, int forced)
|
||
{
|
||
int d = end - start;
|
||
|
||
if (!split_at)
|
||
write_sup(sw, im, num_crop, crops, pal, start, end, stricter, forced);
|
||
else
|
||
{
|
||
while (d >= split_at + min_split)
|
||
{
|
||
d -= split_at;
|
||
write_sup(sw, im, num_crop, crops, pal, start, start + split_at, stricter, forced);
|
||
start += split_at;
|
||
}
|
||
if (d)
|
||
write_sup(sw, im, num_crop, crops, pal, start, start + d, stricter, forced);
|
||
}
|
||
}
|
||
|
||
|
||
struct framerate_entry_s
|
||
{
|
||
char *name;
|
||
char *out_name;
|
||
int rate;
|
||
int drop;
|
||
int fps_num;
|
||
int fps_den;
|
||
};
|
||
|
||
/* Most of the time seems to be spent in AviSynth (about 4/5). */
|
||
int main (int argc, char *argv[])
|
||
{
|
||
struct framerate_entry_s framerates[] = { {"23.976", "23.976", 24, 0, 24000, 1001}
|
||
/*, {"23.976d", "23.976", 24000/1001.0, 1}*/
|
||
, {"24", "24", 24, 0, 24, 1}
|
||
, {"25", "25", 25, 0, 25, 1}
|
||
, {"29.97", "29.97", 30, 0, 30000, 1001}
|
||
/*, {"29.97d", "29.97", 30000/1001.0, 1}*/
|
||
, {"50", "50", 50, 0, 50, 1}
|
||
, {"59.94", "59.94", 60, 0, 60000, 1001}
|
||
/*, {"59.94d", "59.94", 60000/1001.0, 1}*/
|
||
, {NULL, NULL, 0, 0, 0, 0}
|
||
};
|
||
char *avs_filename = NULL;
|
||
char *track_name = "Undefined";
|
||
char *language = "und";
|
||
char *video_format = "1080p";
|
||
char *frame_rate = "23.976";
|
||
char *out_filename[2] = {NULL, NULL};
|
||
char *sup_output_fn = NULL;
|
||
char *xml_output_fn = NULL;
|
||
char *x_offset = "0";
|
||
char *y_offset = "0";
|
||
char *t_offset = "0";
|
||
char *buffer_optimize = "0";
|
||
char *split_after = "0";
|
||
char *minimum_split = "3";
|
||
char *palletize_png = "1";
|
||
char *even_y_string = "0";
|
||
char *auto_crop_image = "1";
|
||
char *ugly_option = "0";
|
||
char *seek_string = "0";
|
||
char *allow_empty_string = "0";
|
||
char *stricter_string = "0";
|
||
char *count_string = "2147483647";
|
||
char *in_img = NULL, *old_img = NULL, *tmp = NULL, *out_buf = NULL;
|
||
char *intc_buf = NULL, *outtc_buf = NULL;
|
||
char *drop_frame = NULL;
|
||
char *mark_forced_string = "0";
|
||
char png_dir[MAX_PATH + 1] = {0};
|
||
crop_t crops[2];
|
||
pic_t pic;
|
||
uint32_t *pal = NULL;
|
||
int out_filename_idx = 0;
|
||
int have_fps = 0;
|
||
int n_crop = 1;
|
||
int split_at = 0;
|
||
int min_split = 3;
|
||
int autocrop = 0;
|
||
int xo, yo, to;
|
||
int fps = 25;
|
||
int count_frames = INT_MAX, last_frame;
|
||
int init_frame = 0;
|
||
int frames;
|
||
int first_frame = -1, start_frame = -1, end_frame = -1;
|
||
int num_of_events = 0;
|
||
int i, c, j;
|
||
int have_line = 0;
|
||
int must_zero = 0;
|
||
int checked_empty;
|
||
int even_y = 0;
|
||
int auto_cut = 0;
|
||
int pal_png = 1;
|
||
int ugly = 0;
|
||
int progress_step = 1000;
|
||
int buffer_opt;
|
||
int bench_start = time(NULL);
|
||
int fps_num = 25, fps_den = 1;
|
||
int sup_output = 0;
|
||
int xml_output = 0;
|
||
int allow_empty = 0;
|
||
int stricter = 0;
|
||
int mark_forced = 0;
|
||
sup_writer_t *sw = NULL;
|
||
avis_input_t *avis_hnd;
|
||
stream_info_t *s_info = malloc(sizeof(stream_info_t));
|
||
event_list_t *events = event_list_new();
|
||
event_t *event;
|
||
FILE *fh;
|
||
|
||
/* Get args */
|
||
if (argc < 2)
|
||
{
|
||
print_usage();
|
||
return 0;
|
||
}
|
||
while (1)
|
||
{
|
||
static struct option long_options[] =
|
||
{ {"output", required_argument, 0, 'o'}
|
||
, {"seek", required_argument, 0, 'j'}
|
||
, {"count", required_argument, 0, 'c'}
|
||
, {"trackname", required_argument, 0, 't'}
|
||
, {"language", required_argument, 0, 'l'}
|
||
, {"video-format", required_argument, 0, 'v'}
|
||
, {"fps", required_argument, 0, 'f'}
|
||
, {"x-offset", required_argument, 0, 'x'}
|
||
, {"y-offset", required_argument, 0, 'y'}
|
||
, {"t-offset", required_argument, 0, 'd'}
|
||
, {"split-at", required_argument, 0, 's'}
|
||
, {"min-split", required_argument, 0, 'm'}
|
||
, {"autocrop", required_argument, 0, 'a'}
|
||
, {"even-y", required_argument, 0, 'e'}
|
||
, {"palette", required_argument, 0, 'p'}
|
||
, {"buffer-opt", required_argument, 0, 'b'}
|
||
, {"ugly", required_argument, 0, 'u'}
|
||
, {"null-xml", required_argument, 0, 'n'}
|
||
, {"stricter", required_argument, 0, 'z'}
|
||
, {"forced", required_argument, 0, 'F'}
|
||
, {0, 0, 0, 0}
|
||
};
|
||
int option_index = 0;
|
||
|
||
c = getopt_long(argc, argv, "o:j:c:t:l:v:f:x:y:d:b:s:m:e:p:a:u:n:z:F:", long_options, &option_index);
|
||
if (c == -1)
|
||
break;
|
||
switch (c)
|
||
{
|
||
case 'o':
|
||
if (out_filename_idx < 2)
|
||
out_filename[out_filename_idx++] = optarg;
|
||
else
|
||
{
|
||
printf( "No more than two output filenames allowed.\nIf more than one is used, the other must have a\ndifferent output format.\r\n");
|
||
exit(0);
|
||
}
|
||
break;
|
||
case 'j':
|
||
seek_string = optarg;
|
||
break;
|
||
case 'c':
|
||
count_string = optarg;
|
||
break;
|
||
case 't':
|
||
track_name = optarg;
|
||
break;
|
||
case 'l':
|
||
language = optarg;
|
||
break;
|
||
case 'v':
|
||
video_format = optarg;
|
||
break;
|
||
case 'f':
|
||
frame_rate = optarg;
|
||
break;
|
||
case 'x':
|
||
x_offset = optarg;
|
||
break;
|
||
case 'y':
|
||
y_offset = optarg;
|
||
break;
|
||
case 'd':
|
||
t_offset = optarg;
|
||
break;
|
||
case 'e':
|
||
even_y_string = optarg;
|
||
break;
|
||
case 'p':
|
||
palletize_png = optarg;
|
||
break;
|
||
case 'a':
|
||
auto_crop_image = optarg;
|
||
break;
|
||
case 'b':
|
||
buffer_optimize = optarg;
|
||
break;
|
||
case 's':
|
||
split_after = optarg;
|
||
break;
|
||
case 'm':
|
||
minimum_split = optarg;
|
||
break;
|
||
case 'u':
|
||
ugly_option = optarg;
|
||
break;
|
||
case 'n':
|
||
allow_empty_string = optarg;
|
||
break;
|
||
case 'z':
|
||
stricter_string = optarg;
|
||
break;
|
||
case 'F':
|
||
mark_forced_string = optarg;
|
||
break;
|
||
default:
|
||
print_usage();
|
||
return 0;
|
||
break;
|
||
}
|
||
}
|
||
if (argc - optind == 1)
|
||
avs_filename = argv[optind];
|
||
else
|
||
{
|
||
printf( "Only a single input file allowed.\r\n");
|
||
return 1;
|
||
}
|
||
|
||
/* Both input and output filenames are required */
|
||
if (avs_filename == NULL)
|
||
{
|
||
print_usage();
|
||
return 0;
|
||
}
|
||
if (out_filename[0] == NULL)
|
||
{
|
||
print_usage();
|
||
return 0;
|
||
}
|
||
|
||
/* Get target output format */
|
||
for (i = 0; i < out_filename_idx; i++)
|
||
{
|
||
if (is_extension(out_filename[i], "xml"))
|
||
{
|
||
xml_output_fn = out_filename[i];
|
||
xml_output++;
|
||
get_dir_path(xml_output_fn, png_dir);
|
||
}
|
||
else if (is_extension(out_filename[i], "sup") || is_extension(out_filename[i], "pgs"))
|
||
{
|
||
sup_output_fn = out_filename[i];
|
||
sup_output++;
|
||
pal_png = 1;
|
||
}
|
||
else
|
||
{
|
||
printf( "Output file extension must be \".xml\", \".sup\" or \".pgs\".\r\n");
|
||
return 1;
|
||
}
|
||
}
|
||
if (sup_output > 1 || xml_output > 1)
|
||
{
|
||
printf( "If more than one output filename is used, they must have\ndifferent output formats.\r\n");
|
||
exit(0);
|
||
}
|
||
|
||
/* Set X and Y offsets, and split value */
|
||
xo = parse_int(x_offset, "x-offset", NULL);
|
||
yo = parse_int(y_offset, "y-offset", NULL);
|
||
pal_png = parse_int(palletize_png, "palette", NULL);
|
||
even_y = parse_int(even_y_string, "even-y", NULL);
|
||
autocrop = parse_int(auto_crop_image, "autocrop", NULL);
|
||
split_at = parse_int(split_after, "split-at", NULL);
|
||
ugly = parse_int(ugly_option, "ugly", NULL);
|
||
allow_empty = parse_int(allow_empty_string, "null-xml", NULL);
|
||
stricter = parse_int(stricter_string, "stricter", NULL);
|
||
init_frame = parse_int(seek_string, "seek", NULL);
|
||
count_frames = parse_int(count_string, "count", NULL);
|
||
min_split = parse_int(minimum_split, "min-split", NULL);
|
||
if (!min_split)
|
||
min_split = 1;
|
||
mark_forced = parse_int(mark_forced_string, "forced", NULL);
|
||
|
||
/* TODO: Sanity check video_format and frame_rate. */
|
||
|
||
/* Get frame rate */
|
||
i = 0;
|
||
while (framerates[i].name != NULL)
|
||
{
|
||
if (!strcasecmp(framerates[i].name, frame_rate))
|
||
{
|
||
frame_rate = framerates[i].out_name;
|
||
fps = framerates[i].rate;
|
||
drop_frame = framerates[i].drop ? "true" : "false";
|
||
fps_num = framerates[i].fps_num;
|
||
fps_den = framerates[i].fps_den;
|
||
have_fps = 1;
|
||
}
|
||
i++;
|
||
}
|
||
if (!have_fps)
|
||
{
|
||
printf( "Error: Invalid framerate (%s).\r\n", frame_rate);
|
||
return 1;
|
||
}
|
||
|
||
/* Get timecode offset. */
|
||
to = parse_tc(t_offset, fps);
|
||
|
||
/* Detect CPU features */
|
||
detect_sse2();
|
||
|
||
/* Get video info and allocate buffer */
|
||
if (open_file_avis(avs_filename, &avis_hnd, s_info))
|
||
{
|
||
print_usage();
|
||
return 1;
|
||
}
|
||
in_img = calloc(s_info->i_width * s_info->i_height * 4 + 16 * 2, sizeof(char)); /* allocate + 16 for alignment, and + n * 16 for over read/write */
|
||
old_img = calloc(s_info->i_width * s_info->i_height * 4 + 16 * 2, sizeof(char)); /* see above */
|
||
out_buf = calloc(s_info->i_width * s_info->i_height * 4 + 16 * 2, sizeof(char));
|
||
|
||
/* Check minimum size */
|
||
if (s_info->i_width < 8 || s_info->i_height < 8)
|
||
{
|
||
printf( "Error: Video dimensions below 8x8 (%dx%d).\r\n", s_info->i_width, s_info->i_height);
|
||
return 1;
|
||
}
|
||
|
||
/* Align buffers */
|
||
in_img = in_img + (short)(16 - ((long)in_img % 16));
|
||
old_img = old_img + (short)(16 - ((long)old_img % 16));
|
||
out_buf = out_buf + (short)(16 - ((long)out_buf % 16));
|
||
|
||
/* Set up buffer (non-)optimization */
|
||
buffer_opt = parse_int(buffer_optimize, "buffer-opt", NULL);
|
||
pic.b = out_buf;
|
||
pic.w = s_info->i_width;
|
||
pic.h = s_info->i_height;
|
||
pic.s = s_info->i_width;
|
||
n_crop = 1;
|
||
crops[0].x = 0;
|
||
crops[0].y = 0;
|
||
crops[0].w = pic.w;
|
||
crops[0].h = pic.h;
|
||
|
||
/* Get frame number */
|
||
frames = get_frame_total_avis(avis_hnd);
|
||
if (count_frames + init_frame > frames)
|
||
{
|
||
count_frames = frames - init_frame;
|
||
}
|
||
last_frame = count_frames + init_frame;
|
||
|
||
/* No frames mean nothing to do */
|
||
if (count_frames < 1)
|
||
{
|
||
printf( "No frames found.\r\n");
|
||
return 0;
|
||
}
|
||
|
||
/* Set progress step */
|
||
if (count_frames < 1000)
|
||
{
|
||
if (count_frames > 200)
|
||
progress_step = 50;
|
||
else if (count_frames > 50)
|
||
progress_step = 10;
|
||
else
|
||
progress_step = 1;
|
||
}
|
||
|
||
/* Open SUP writer, if applicable */
|
||
if (sup_output)
|
||
sw = new_sup_writer(sup_output_fn, pic.w, pic.h, fps_num, fps_den);
|
||
|
||
/* Process frames */
|
||
for (i = init_frame; i < last_frame; i++)
|
||
{
|
||
if (read_frame_avis(in_img, avis_hnd, i))
|
||
{
|
||
printf( "Error reading frame.\r\n");
|
||
return 1;
|
||
}
|
||
checked_empty = 0;
|
||
|
||
/* Progress indicator */
|
||
if (i % (count_frames / progress_step) == 0)
|
||
{
|
||
printf( "Progress: %d/%d - Lines: %d\r\n", i - init_frame, count_frames, num_of_events);
|
||
fflush(stdout);
|
||
}
|
||
|
||
/* If we are outside any lines, check for empty frames first */
|
||
if (!have_line)
|
||
{
|
||
if (is_empty(s_info, in_img))
|
||
continue;
|
||
else
|
||
checked_empty = 1;
|
||
}
|
||
|
||
/* Check for duplicate, unless first frame */
|
||
if ((i != init_frame) && have_line && is_identical(s_info, in_img, old_img))
|
||
continue;
|
||
/* Mark frames that were not used as new image in comparison to have transparent pixels zeroed */
|
||
else if (!(i && have_line))
|
||
must_zero = 1;
|
||
|
||
/* Not a dup, write end-of-line, if we had a line before */
|
||
if (have_line)
|
||
{
|
||
if (sup_output)
|
||
{
|
||
assert(pal != NULL);
|
||
write_sup_wrapper(sw, (uint8_t *)out_buf, n_crop, crops, pal, start_frame + to, i + to, split_at, min_split, stricter, mark_forced);
|
||
if (!xml_output)
|
||
free(pal);
|
||
pal = NULL;
|
||
}
|
||
if (xml_output)
|
||
add_event_xml(events, split_at, min_split, start_frame + to, i + to, n_crop, crops, mark_forced);
|
||
end_frame = i;
|
||
have_line = 0;
|
||
}
|
||
|
||
/* Check for empty frame, if we didn't before */
|
||
if (!checked_empty && is_empty(s_info, in_img))
|
||
continue;
|
||
|
||
/* Zero transparent pixels, if needed */
|
||
if (must_zero)
|
||
zero_transparent(s_info, in_img);
|
||
must_zero = 0;
|
||
|
||
/* Not an empty frame, start line */
|
||
have_line = 1;
|
||
start_frame = i;
|
||
swap_rb(s_info, in_img, out_buf);
|
||
if (buffer_opt)
|
||
n_crop = auto_split(pic, crops, ugly, even_y);
|
||
else if (autocrop)
|
||
{
|
||
crops[0].x = 0;
|
||
crops[0].y = 0;
|
||
crops[0].w = pic.w;
|
||
crops[0].h = pic.h;
|
||
auto_crop(pic, crops);
|
||
}
|
||
if ((buffer_opt || autocrop) && even_y)
|
||
enforce_even_y(crops, n_crop);
|
||
if ((pal_png || sup_output) && pal == NULL)
|
||
pal = palletize(out_buf, s_info->i_width, s_info->i_height);
|
||
if (xml_output)
|
||
for (j = 0; j < n_crop; j++)
|
||
write_png(png_dir, start_frame, (uint8_t *)out_buf, s_info->i_width, s_info->i_height, j, pal, crops[j]);
|
||
if (pal_png && xml_output && !sup_output)
|
||
{
|
||
free(pal);
|
||
pal = NULL;
|
||
}
|
||
num_of_events++;
|
||
if (first_frame == -1)
|
||
first_frame = i;
|
||
|
||
/* Save image for next comparison. */
|
||
tmp = in_img;
|
||
in_img = old_img;
|
||
old_img = tmp;
|
||
}
|
||
|
||
printf( "Progress: %d/%d - Lines: %d - Done\r\n", i - init_frame, count_frames, num_of_events);
|
||
|
||
/* Add last event, if available */
|
||
if (have_line)
|
||
{
|
||
if (sup_output)
|
||
{
|
||
assert(pal != NULL);
|
||
write_sup_wrapper(sw, (uint8_t *)out_buf, n_crop, crops, pal, start_frame + to, i - 1 + to, split_at, min_split, stricter, mark_forced);
|
||
if (!xml_output)
|
||
free(pal);
|
||
pal = NULL;
|
||
}
|
||
if (xml_output)
|
||
{
|
||
add_event_xml(events, split_at, min_split, start_frame + to, i - 1 + to, n_crop, crops, mark_forced);
|
||
free(pal);
|
||
pal = NULL;
|
||
}
|
||
auto_cut = 1;
|
||
end_frame = i - 1;
|
||
}
|
||
|
||
if (sup_output)
|
||
{
|
||
close_sup_writer(sw);
|
||
}
|
||
|
||
if (xml_output)
|
||
{
|
||
/* Check if we actually have any events */
|
||
if (first_frame == -1)
|
||
{
|
||
if (!allow_empty)
|
||
{
|
||
printf( "No events detected. Cowardly refusing to write XML file.\r\n");
|
||
return 0;
|
||
}
|
||
else
|
||
{
|
||
first_frame = 0;
|
||
end_frame = 0;
|
||
}
|
||
}
|
||
|
||
/* Initialize timecode buffers */
|
||
intc_buf = calloc(12, 1);
|
||
outtc_buf = calloc(12, 1);
|
||
|
||
/* Creating output file */
|
||
if ((fh = fopen(xml_output_fn, "w")) == 0)
|
||
{
|
||
perror("Error opening output XML file");
|
||
return 1;
|
||
}
|
||
|
||
/* Write XML header */
|
||
mk_timecode(first_frame + to, fps, intc_buf);
|
||
mk_timecode(end_frame + to + auto_cut, fps, outtc_buf);
|
||
fprintf(fh, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||
"<BDN Version=\"0.93\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
|
||
"xsi:noNamespaceSchemaLocation=\"BD-03-006-0093b BDN File Format.xsd\">\n"
|
||
"<Description>\n"
|
||
"<Name Title=\"%s\" Content=\"\"/>\n"
|
||
"<Language Code=\"%s\"/>\n"
|
||
"<Format VideoFormat=\"%s\" FrameRate=\"%s\" DropFrame=\"%s\"/>\n"
|
||
"<Events LastEventOutTC=\"%s\" FirstEventInTC=\"%s\"\n", track_name, language, video_format, frame_rate, drop_frame, outtc_buf, intc_buf);
|
||
|
||
mk_timecode(0, fps, intc_buf);
|
||
mk_timecode(frames + to, fps, outtc_buf);
|
||
fprintf(fh, "ContentInTC=\"%s\" ContentOutTC=\"%s\" NumberofEvents=\"%d\" Type=\"Graphic\"/>\n"
|
||
"</Description>\n"
|
||
"<Events>\n", intc_buf, outtc_buf, num_of_events);
|
||
|
||
/* Write XML events */
|
||
if (!event_list_empty(events))
|
||
{
|
||
event = event_list_first(events);
|
||
do
|
||
{
|
||
mk_timecode(event->start_frame, fps, intc_buf);
|
||
mk_timecode(event->end_frame, fps, outtc_buf);
|
||
|
||
if (auto_cut && event->end_frame == frames - 1)
|
||
{
|
||
mk_timecode(event->end_frame + 1, fps, outtc_buf);
|
||
}
|
||
|
||
fprintf(fh, "<Event Forced=\"%s\" InTC=\"%s\" OutTC=\"%s\">\n", (event->forced ? "True" : "False"), intc_buf, outtc_buf);
|
||
for (i = 0; i < event->graphics; i++)
|
||
{
|
||
fprintf(fh, "<Graphic Width=\"%d\" Height=\"%d\" X=\"%d\" Y=\"%d\">%08d_%d.png</Graphic>\n", event->c[i].w, event->c[i].h, xo + event->c[i].x, yo + event->c[i].y, event->image_number - to, i);
|
||
}
|
||
fprintf(fh, "</Event>\n");
|
||
event = event_list_next(events);
|
||
}
|
||
while (event != NULL);
|
||
}
|
||
|
||
/* Write XML footer */
|
||
fprintf(fh, "</Events>\n</BDN>\n");
|
||
|
||
/* Close XML file */
|
||
fclose(fh);
|
||
}
|
||
|
||
/* Cleanup */
|
||
close_file_avis(avis_hnd);
|
||
|
||
/* Give runtime */
|
||
if (0)
|
||
printf( "Time elapsed: %lu\r\n", time(NULL) - bench_start);
|
||
|
||
return 0;
|
||
}
|
||
|