You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
718 lines
17 KiB
718 lines
17 KiB
/*
|
|
* avisplit.c
|
|
*
|
|
* Copyright (C) Thomas Oestreich - June 2001
|
|
*
|
|
* This file is part of transcode, a video stream processing tool
|
|
*
|
|
* transcode 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 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* transcode 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 GNU Make; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
*/
|
|
|
|
#include "transcode.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include "limits.h"
|
|
#include <string.h>
|
|
#include "buffer.h"
|
|
#include "avilib/avilib.h"
|
|
#include "libtc/framecode.h"
|
|
#include "aud_scan_avi.h"
|
|
|
|
#define EXE "avisplit"
|
|
#define MBYTE (1<<20)
|
|
|
|
/* AVI_info is no longer in avilib */
|
|
void AVI_info(avi_t *avifile);
|
|
|
|
void version(void)
|
|
{
|
|
printf("%s (%s v%s) (C) 2001-2003 Thomas Oestreich,"
|
|
" 2003-2010 Transcode Team\n",
|
|
EXE, PACKAGE, VERSION);
|
|
}
|
|
|
|
static void usage(int status)
|
|
{
|
|
version();
|
|
printf("\nUsage: %s [options]\n", EXE);
|
|
printf(" -i name file name\n");
|
|
printf(" -s size de-chunk based on size in MB (0=dechunk)\n");
|
|
printf(" -H n split only first n chunks [all]\n");
|
|
printf(" -t s1-s2[,s3-s4,..] de-chunk based on time/framecode (n:m:l.k) [off]\n");
|
|
printf(" -c merge chunks on-the-fly for option -t [off]\n");
|
|
printf(" -m force split at upper limit for option -t [off]\n");
|
|
printf(" -o base split to base-%%04d.avi [name-%%04d]\n");
|
|
printf(" -b n handle vbr audio [autodetect]\n");
|
|
printf(" -f FILE read AVI comments from FILE [off]\n");
|
|
printf(" -v print version\n");
|
|
exit(status);
|
|
}
|
|
|
|
// buffer
|
|
static char data[SIZE_RGB_FRAME];
|
|
static char out_file[1024];
|
|
static char *comfile = NULL;
|
|
int is_vbr = 1;
|
|
|
|
enum split_type
|
|
{
|
|
SPLIT_BY_SIZE,
|
|
SPLIT_BY_TIME
|
|
};
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
|
|
avi_t *in=NULL;
|
|
avi_t *out=NULL;
|
|
|
|
char *in_file=NULL;
|
|
|
|
long i, frames, bytes, bytes_to_key, tmpreturn;
|
|
|
|
uint64_t size=0;
|
|
|
|
double fsize=0.0, fps;
|
|
|
|
char *codec;
|
|
|
|
int j, n, m, key, k;
|
|
|
|
int key_boundary=1;
|
|
|
|
int chunk=0, is_open, ch, split_next=INT_MAX;
|
|
|
|
long rate, mp3rate=0L;
|
|
|
|
int width, height, format=0, chan, bits;
|
|
|
|
char *base=NULL;
|
|
char argcopy[1024];
|
|
|
|
/* added variables */
|
|
long start_audio_keyframe[ AVI_MAX_TRACKS ];
|
|
long byte_count_audio[ AVI_MAX_TRACKS ];
|
|
long byte_count_at_start[ AVI_MAX_TRACKS ];
|
|
static char *single_output_file=NULL;
|
|
struct fc_time * ttime = NULL;
|
|
struct fc_time * tstart = NULL;
|
|
int start_keyframe=0;
|
|
int split_option=0;
|
|
int first_frame=1;
|
|
int num_frames;
|
|
int didread = 0;
|
|
|
|
double aud_ms[ AVI_MAX_TRACKS ];
|
|
double aud_ms_w[ AVI_MAX_TRACKS ];
|
|
|
|
int vid_chunks=0;
|
|
//int aud_chunks=0;
|
|
double vid_ms_w = 0.0;
|
|
double vid_ms = 0.0;
|
|
|
|
char separator[] = ",";
|
|
|
|
ac_init(AC_ALL);
|
|
|
|
if(argc==1) usage(EXIT_FAILURE);
|
|
memset(byte_count_at_start, 0 , sizeof(long)*AVI_MAX_TRACKS);
|
|
|
|
while ((ch = getopt(argc, argv, "b:mco:vs:i:f:t:H:?h")) != -1) {
|
|
|
|
switch (ch) {
|
|
|
|
case 'b':
|
|
|
|
if(optarg[0]=='-') usage(EXIT_FAILURE);
|
|
is_vbr = atoi(optarg);
|
|
|
|
if(is_vbr<0) usage(EXIT_FAILURE);
|
|
|
|
break;
|
|
|
|
case 'c': // cat
|
|
single_output_file = out_file;
|
|
break;
|
|
|
|
case 'm':
|
|
key_boundary = 0;
|
|
break;
|
|
|
|
case 'H':
|
|
|
|
if(optarg[0]=='-') usage(EXIT_FAILURE);
|
|
split_next = atoi(optarg);
|
|
|
|
if(split_next <= 0) {
|
|
fprintf(stderr, "(%s) invalid parameter for option -H\n", __FILE__);
|
|
exit(0);
|
|
}
|
|
break;
|
|
|
|
case 'i':
|
|
|
|
if(optarg[0]=='-') usage(EXIT_FAILURE);
|
|
in_file=optarg;
|
|
|
|
break;
|
|
|
|
case 's':
|
|
|
|
if(optarg[0]=='-') usage(EXIT_FAILURE);
|
|
chunk = atoi(optarg);
|
|
split_option=SPLIT_BY_SIZE;
|
|
|
|
break;
|
|
|
|
case 't':
|
|
split_option=SPLIT_BY_TIME;
|
|
strncpy (argcopy, optarg, 1024);
|
|
|
|
break;
|
|
|
|
case 'o':
|
|
|
|
if(optarg[0]=='-') usage(EXIT_FAILURE);
|
|
base = optarg;
|
|
|
|
break;
|
|
|
|
case 'f':
|
|
|
|
if(optarg[0]=='-') usage(EXIT_FAILURE);
|
|
comfile = optarg;
|
|
|
|
break;
|
|
|
|
case 'v':
|
|
version();
|
|
exit(0);
|
|
break;
|
|
|
|
case 'h':
|
|
usage(EXIT_SUCCESS);
|
|
|
|
default:
|
|
usage(EXIT_FAILURE);
|
|
}
|
|
}
|
|
/*
|
|
* check
|
|
*/
|
|
switch (split_option) {
|
|
case SPLIT_BY_SIZE:
|
|
|
|
if(in_file==NULL || chunk < 0) usage(EXIT_FAILURE);
|
|
|
|
break;
|
|
|
|
case SPLIT_BY_TIME:
|
|
|
|
if(in_file==NULL) usage(EXIT_FAILURE);
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
// open file
|
|
if(NULL == (in = AVI_open_input_file(in_file,1))) {
|
|
AVI_print_error("AVI open");
|
|
exit(1);
|
|
}
|
|
|
|
// read video info;
|
|
|
|
AVI_info(in);
|
|
|
|
// read video info;
|
|
|
|
frames = AVI_video_frames(in);
|
|
width = AVI_video_width(in);
|
|
height = AVI_video_height(in);
|
|
|
|
fps = AVI_frame_rate(in);
|
|
codec = AVI_video_compressor(in);
|
|
rate = AVI_audio_rate(in);
|
|
chan = AVI_audio_channels(in);
|
|
bits = AVI_audio_bits(in);
|
|
|
|
for (k = 0; k<AVI_MAX_TRACKS; k++)
|
|
aud_ms[k] = 0.0;
|
|
|
|
for (k = 0; k<AVI_MAX_TRACKS; k++)
|
|
aud_ms_w[k] = 0.0;
|
|
|
|
switch (split_option) {
|
|
|
|
case SPLIT_BY_SIZE:
|
|
// no file open yet
|
|
is_open=0;
|
|
// index of split files
|
|
j=0;
|
|
// start frame
|
|
i=0;
|
|
|
|
//some header may be broken
|
|
if(frames<=0) frames=INT_MAX;
|
|
|
|
for (n=0; n<frames; ++n) {
|
|
|
|
// read video frame
|
|
bytes = AVI_read_frame(in, data, &key);
|
|
|
|
if(bytes < 0) {
|
|
fprintf(stderr, "%d (%ld)\n", n, bytes);
|
|
AVI_print_error("AVI read video frame");
|
|
break;
|
|
}
|
|
|
|
//check for closing outputfile
|
|
|
|
if(key && is_open && n && split_next) {
|
|
|
|
// loop through from this keyframe to the next to find out this chunks real size
|
|
// save bytes from first query
|
|
bytes_to_key = bytes;
|
|
|
|
//Loop until next keyframe
|
|
for(m=0; (!key || m==0); ++m) {
|
|
//video
|
|
tmpreturn = AVI_audio_size(in, n+m);
|
|
if (tmpreturn == -1) break;
|
|
bytes_to_key += tmpreturn;
|
|
//audio
|
|
tmpreturn = AVI_read_frame(in, NULL, &key);
|
|
if (tmpreturn == -1) break;
|
|
bytes_to_key += tmpreturn;
|
|
}
|
|
//rewind to correct position, the last keyframe.
|
|
AVI_set_video_position(in, n);
|
|
|
|
size = AVI_bytes_written(out);
|
|
fsize = ((double) size)/MBYTE;
|
|
|
|
if((size + bytes_to_key) > (uint64_t)(chunk*MBYTE)) {
|
|
|
|
// limit exceeded, close file
|
|
|
|
fprintf(stderr, "\n");
|
|
AVI_close(out);
|
|
out=NULL;
|
|
--split_next; //0 for trailer split mode after first chunk.
|
|
is_open=0;
|
|
++j;
|
|
i=n;
|
|
}
|
|
}
|
|
|
|
|
|
// progress
|
|
if(out) {
|
|
vid_ms = vid_chunks*1000.0/fps;
|
|
|
|
fprintf(stderr, "[%s] (%06ld-%06d), size %4.1f MB. (V/A) (%.0f/%.0f)ms\r", out_file, i, n-1, ((double) AVI_bytes_written(out))/MBYTE, vid_ms, aud_ms [0]);
|
|
}
|
|
|
|
if (split_next == 0) {
|
|
if(in != NULL)
|
|
AVI_close(in);
|
|
in=NULL;
|
|
if(out != NULL)
|
|
AVI_close(out);
|
|
|
|
return (0);
|
|
}
|
|
|
|
// need new output file
|
|
if(!is_open) {
|
|
|
|
if(base == NULL || strlen(base)==0) {
|
|
tc_snprintf(out_file, sizeof(out_file), "%s-%04d", in_file, j);
|
|
} else {
|
|
tc_snprintf(out_file, sizeof(out_file), "%s-%04d.avi", base, j);
|
|
}
|
|
|
|
// prepare output file
|
|
|
|
if(NULL == (out = AVI_open_output_file(out_file))) {
|
|
AVI_print_error("AVI open");
|
|
exit(1);
|
|
}
|
|
|
|
AVI_set_video(out, width, height, fps, codec);
|
|
if (comfile!=NULL)
|
|
AVI_set_comment_fd(out, open(comfile, O_RDONLY));
|
|
|
|
for(k=0; k< AVI_audio_tracks(in); ++k) {
|
|
|
|
AVI_set_audio_track(in, k);
|
|
|
|
rate = AVI_audio_rate(in);
|
|
chan = AVI_audio_channels(in);
|
|
bits = AVI_audio_bits(in);
|
|
|
|
format = AVI_audio_format(in);
|
|
mp3rate= AVI_audio_mp3rate(in);
|
|
|
|
|
|
//set next track of output file
|
|
AVI_set_audio_track(out, j);
|
|
AVI_set_audio(out, chan, rate, bits, format, mp3rate);
|
|
AVI_set_audio_vbr(out, AVI_get_audio_vbr(in));
|
|
}
|
|
|
|
is_open=1;
|
|
}
|
|
|
|
//write frame
|
|
|
|
if(AVI_write_frame(out, data, bytes, key)<0) {
|
|
AVI_print_error("AVI write video frame");
|
|
return(-1);
|
|
}
|
|
|
|
vid_chunks++;
|
|
vid_ms = vid_chunks*1000.0/fps;
|
|
|
|
//audio
|
|
for(k=0; k< AVI_audio_tracks(in); ++k) {
|
|
|
|
AVI_set_audio_track(in, k);
|
|
AVI_set_audio_track(out, k);
|
|
|
|
sync_audio_video_avi2avi(vid_ms, &aud_ms[k], in, out);
|
|
}
|
|
|
|
}//process all frames
|
|
|
|
if(in != NULL)
|
|
AVI_close(in);
|
|
|
|
size = AVI_bytes_written(out);
|
|
vid_ms = vid_chunks*1000.0/fps;
|
|
|
|
fprintf(stderr, "[%s] (%06ld-%06d), size %4.1f MB. vid=%8.2f ms aud=%8.2f ms\n", out_file, i, n-1, ((double) AVI_bytes_written(out))/MBYTE, vid_ms, aud_ms[0]);
|
|
|
|
if(out != NULL)
|
|
AVI_close(out);
|
|
|
|
break;
|
|
|
|
// XXX: use aud_ms like above
|
|
case SPLIT_BY_TIME:
|
|
|
|
if( parse_fc_time_string( argcopy, fps, separator, 1, &ttime ) == -1 )
|
|
usage(EXIT_FAILURE);
|
|
/*
|
|
* pointer into the fc_list
|
|
*/
|
|
tstart = ttime;
|
|
/*
|
|
* index of split files
|
|
*/
|
|
j = 0;
|
|
/*
|
|
* no single output file
|
|
*/
|
|
if( single_output_file != NULL ) {
|
|
|
|
if(base == NULL || strlen(base)==0) {
|
|
tc_snprintf(out_file, sizeof(out_file), "%s-%04d", in_file, j++ );
|
|
} else {
|
|
tc_snprintf(out_file, sizeof(out_file), "%s", base );
|
|
}
|
|
if( ( out = AVI_open_output_file( out_file ) ) == NULL ) {
|
|
AVI_print_error( "AVI open" );
|
|
exit( 1 );
|
|
}
|
|
/*
|
|
* set video params in the output file
|
|
*/
|
|
AVI_set_video( out, width, height, fps, codec );
|
|
if (comfile!=NULL)
|
|
AVI_set_comment_fd(out, open(comfile, O_RDONLY));
|
|
/*
|
|
* set audio params in the output file
|
|
*/
|
|
for( k = 0; k < AVI_audio_tracks( in ); k++ ) {
|
|
|
|
AVI_set_audio_track( in, k );
|
|
AVI_set_audio_track( out, k );
|
|
|
|
rate = AVI_audio_rate ( in );
|
|
chan = AVI_audio_channels( in );
|
|
bits = AVI_audio_bits ( in );
|
|
format = AVI_audio_format ( in );
|
|
mp3rate= AVI_audio_mp3rate ( in );
|
|
|
|
AVI_set_audio( out, chan, rate, bits, format, mp3rate );
|
|
AVI_set_audio_vbr( out, AVI_get_audio_vbr(in));
|
|
}
|
|
}
|
|
/*
|
|
* process next fc_time_string
|
|
*/
|
|
while( ttime != NULL ) {
|
|
first_frame = 1;
|
|
start_keyframe = 0;
|
|
num_frames = ttime->etf - ttime->stf;
|
|
|
|
/*
|
|
* reset input file
|
|
*/
|
|
AVI_seek_start( in );
|
|
for( k = 0; k < AVI_audio_tracks( in ); k++ ) {
|
|
byte_count_audio[ k ] = 0;
|
|
start_audio_keyframe[ k ] = 0;
|
|
AVI_set_audio_track (in, k);
|
|
AVI_set_audio_position_index (in, 0);
|
|
}
|
|
AVI_set_audio_track (in, 0);
|
|
// reset counters
|
|
vid_chunks = 0;
|
|
vid_ms_w = 0.0;
|
|
vid_ms = 0.0;
|
|
|
|
for (k = 0; k<AVI_MAX_TRACKS; k++) {
|
|
aud_ms_w[k] = 0.0;
|
|
aud_ms[k] = 0.0;
|
|
}
|
|
|
|
|
|
printf("\nProcessing %d frames %4d to %4d.", num_frames, ttime->stf, ttime->etf);
|
|
/*
|
|
* some header may be broken
|
|
*/
|
|
if( frames <= 0 )
|
|
frames=INT_MAX;
|
|
/*
|
|
* not a single output file
|
|
*/
|
|
if( single_output_file == NULL ) {
|
|
/*
|
|
* prepare output file
|
|
*/
|
|
if( base == NULL || strlen( base ) == 0 ) {
|
|
tc_snprintf( out_file, sizeof(out_file), "%s-%04d", in_file, j++ );
|
|
}
|
|
else {
|
|
tc_snprintf( out_file, sizeof(out_file), "%s-%04d", base, j++ );
|
|
}
|
|
|
|
if( ( out = AVI_open_output_file( out_file ) ) == NULL ) {
|
|
AVI_print_error( "AVI open" );
|
|
exit( 1 );
|
|
}
|
|
/*
|
|
* set video params in the output file
|
|
*/
|
|
AVI_set_video( out, width, height, fps, codec );
|
|
if (comfile!=NULL)
|
|
AVI_set_comment_fd(out, open(comfile, O_RDONLY));
|
|
/*
|
|
* set audio params in the output file
|
|
*/
|
|
for( k = 0; k < AVI_audio_tracks( in ); k++ ) {
|
|
AVI_set_audio_track( in, k );
|
|
|
|
rate = AVI_audio_rate ( in );
|
|
chan = AVI_audio_channels( in );
|
|
bits = AVI_audio_bits ( in );
|
|
format = AVI_audio_format ( in );
|
|
mp3rate = AVI_audio_mp3rate ( in );
|
|
|
|
AVI_set_audio_track( out, k );
|
|
AVI_set_audio( out, chan, rate, bits, format, mp3rate );
|
|
AVI_set_audio_vbr( out, AVI_get_audio_vbr(in) );
|
|
}
|
|
}
|
|
/*
|
|
* process all frames
|
|
*/
|
|
for( n = 0; n < frames; n++) {
|
|
/*
|
|
* read video frame
|
|
*/
|
|
bytes = AVI_read_frame( in, data, &key );
|
|
if( bytes < 0 ) {
|
|
fprintf( stderr, "%d (%ld)\n", n, bytes );
|
|
AVI_print_error( "AVI read video frame" );
|
|
break;
|
|
}
|
|
|
|
vid_ms = (n+1)*1000.0/fps;
|
|
|
|
/*
|
|
* store the key frame
|
|
*/
|
|
if( n <= ttime->stf && key ) {
|
|
start_keyframe = n;
|
|
vid_ms_w = n*1000.0/fps;
|
|
}
|
|
/*
|
|
* read audio frame
|
|
*/
|
|
for( k = 0; k < AVI_audio_tracks( in ); k++ ) {
|
|
|
|
double tms = aud_ms[k];
|
|
AVI_set_audio_track( in, k );
|
|
|
|
byte_count_audio[ k ] = AVI_get_audio_position_index(in);
|
|
|
|
if (!didread) {
|
|
sync_audio_video_avi2avi_ro (vid_ms, &aud_ms[k], in);
|
|
}
|
|
|
|
/*
|
|
* store the key frame
|
|
*/
|
|
if( n <= ttime->stf && key ) {
|
|
start_audio_keyframe[ k ] = byte_count_audio[ k ];
|
|
aud_ms_w[k] = tms;
|
|
}
|
|
|
|
}
|
|
/*
|
|
* if one of the preferred frames write frame (video+audio)
|
|
* but don't stop until the next keyframe
|
|
*/
|
|
if( n >= ttime->stf && ( n <= ttime->etf || ( n >= ttime->stf && ! key ) ) ) {
|
|
/*
|
|
* do the following ONLY for the first frame
|
|
*/
|
|
if( first_frame ) {
|
|
/*
|
|
* rewind n to point to the last keyframe
|
|
*/
|
|
printf( "\nFirst Setting start frame to: %d\n", start_keyframe );
|
|
n = start_keyframe;
|
|
fc_set_start_time( ttime, n );
|
|
/*
|
|
* first the video
|
|
*/
|
|
AVI_set_video_position( in, start_keyframe );
|
|
/*
|
|
* then the audio
|
|
*/
|
|
//printf("Start Audio (%ld)\n", start_audio_keyframe[ 0 ]);
|
|
for( k = 0; k < AVI_audio_tracks( in ); k++ ) {
|
|
AVI_set_audio_track( in, k );
|
|
//AVI_set_audio_position( in, start_audio_keyframe[ k ] );
|
|
AVI_set_audio_position_index( in, start_audio_keyframe[ k ]);
|
|
aud_ms[k] = aud_ms_w[k];
|
|
}
|
|
/*
|
|
* re-read video and audio from rewound position
|
|
*/
|
|
bytes = AVI_read_frame( in, data, &key );
|
|
|
|
// count the frame which will be written also this, too
|
|
vid_ms = vid_ms_w+1000.0/fps;
|
|
|
|
//printf("start_frame (%d) (%f) (%f)\n", n, vid_ms, aud_ms[0]);
|
|
|
|
first_frame = 0;
|
|
}
|
|
/*
|
|
* do the write
|
|
*/
|
|
if( AVI_write_frame( out, data, bytes, key ) < 0 ) {
|
|
AVI_print_error( "AVI write video frame" );
|
|
return( -1 );
|
|
}
|
|
/*
|
|
vid_chunks++;
|
|
vid_ms_w = vid_chunks*1000.0/fps;
|
|
*/
|
|
|
|
//printf("Before Enter (%d) (%f) (%f)\n", n, vid_ms, aud_ms[0]);
|
|
for( k = 0; k < AVI_audio_tracks( in ); k++ ) {
|
|
|
|
AVI_set_audio_track( in, k );
|
|
AVI_set_audio_track( out, k );
|
|
|
|
sync_audio_video_avi2avi (vid_ms, &aud_ms[k], in, out);
|
|
} // foreach audio track
|
|
|
|
didread = 1;
|
|
/*
|
|
* print our progress
|
|
*/
|
|
printf( "[%s] (%06d-%06d)\r", out_file, start_keyframe, n);
|
|
} else {
|
|
didread = 0;
|
|
}
|
|
|
|
if( key_boundary ) {
|
|
if( n > ttime->etf && key ) {
|
|
printf( "\n" );
|
|
break;
|
|
}
|
|
} else {
|
|
if( n > ttime->etf) {
|
|
printf( "\n" );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* if we're using split files
|
|
* close output file
|
|
*/
|
|
if( single_output_file == NULL ) {
|
|
if( out != NULL )
|
|
AVI_close( out );
|
|
}
|
|
|
|
ttime = ttime->next;
|
|
|
|
printf( "\nSetting end frame to: %d | cnt(%ld)\n", n - 1, byte_count_audio[0] );
|
|
}
|
|
|
|
if( in != NULL ) AVI_close( in );
|
|
/*
|
|
* close up single output file
|
|
*/
|
|
if( single_output_file != NULL ) {
|
|
if( out != NULL ) AVI_close( out );
|
|
}
|
|
|
|
if( tstart != NULL )
|
|
free_fc_time( tstart );
|
|
|
|
printf( "\n" );
|
|
|
|
break;
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-file-style: "stroustrup"
|
|
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*
|
|
* vim: expandtab shiftwidth=4:
|
|
*/
|