Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
802 lines (610 sloc) 19.5 KB
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdbool.h>
#include <getopt.h>
#include <limits.h>
#include <sys/mman.h>
#include <inttypes.h>
#include <errno.h>
#include <inttypes.h>
#ifdef __linux__
#include <linux/cdrom.h>
#include "dvd_drive.h"
#endif
#include <dvdread/dvd_reader.h>
#include <dvdread/ifo_read.h>
#include "mpeg2dec/mpeg2.h"
#include "mpeg2dec/mpeg2convert.h"
#include "dvd_info.h"
#include "dvd_device.h"
#include "dvd_drive.h"
#include "dvd_vmg_ifo.h"
#include "dvd_track.h"
#include "dvd_chapter.h"
#include "dvd_cell.h"
#include "dvd_video.h"
#include "dvd_audio.h"
#include "dvd_subtitles.h"
#include "dvd_time.h"
#ifndef DVD_VIDEO_LB_LEN
#define DVD_VIDEO_LB_LEN 2048
#endif
// libmpeg2 reads 4096 bytes at a time, do the same here
#define DVD_READ_BLOCKS 1
#define DVD_BUFFER_SIZE ( DVD_VIDEO_LB_LEN * DVD_READ_BLOCKS )
// libmpeg2
static FILE *mpeg_file = NULL;
static int demux_track = 0xe0;
static int demux_pid = 0;
static char mpeg_filename[] = "dvd_extract.mpg";
#define DEMUX_PAYLOAD_START 1
/*
* libmpeg2:
*
* The demuxer keeps some state between calls:
*
* if "state" = DEMUX_HEADER, then "head_buf" contains the first
* "bytes" bytes from some header.
* if "state" == DEMUX_DATA, then we need to copy "bytes" bytes
* of ES data before the next header.
* if "state" == DEMUX_SKIP, then we need to skip "bytes" bytes
* of data before the next header.
*
* NEEDBYTES makes sure we have the requested number of bytes for a
* header. If we don't, it copies what we have into head_buf and returns,
* so that when we come back with more data we finish decoding this header.
*
* DONEBYTES updates "buf" to point after the header we just parsed.
*/
static int demux(uint8_t *buf, uint8_t *end, int flags);
int main(int, char **);
void dvd_track_info(struct dvd_track *dvd_track, const uint16_t track_number, const ifo_handle_t *vmg_ifo, const ifo_handle_t *vts_ifo);
void print_usage(char *binary);
struct dvd_copy {
uint16_t track;
uint8_t first_chapter;
uint8_t last_chapter;
uint8_t first_cell;
uint8_t last_cell;
ssize_t blocks;
ssize_t filesize;
char filename[PATH_MAX];
int fd;
};
int main(int argc, char **argv) {
/**
* Parse options
*/
bool opt_track_number = false;
bool opt_chapter_number = false;
uint16_t arg_track_number = 0;
int long_index = 0;
int opt = 0;
opterr = 1;
const char *str_options;
str_options = "c:ht:";
uint8_t arg_first_chapter = 1;
uint8_t arg_last_chapter = 99;
char *token = NULL;
struct dvd_copy dvd_copy;
mpeg_file = fopen(mpeg_filename, "w+");
if(!mpeg_file) {
printf("Could not open file %s\n", mpeg_filename);
return 1;
}
struct option long_options[] = {
{ "chapters", required_argument, 0, 'c' },
{ "track", required_argument, 0, 't' },
{ 0, 0, 0, 0 }
};
dvd_copy.track = 1;
dvd_copy.first_chapter = 1;
dvd_copy.last_chapter = 99;
dvd_copy.first_cell = 1;
dvd_copy.last_cell = 1;
dvd_copy.blocks = 0;
dvd_copy.filesize = 0;
while((opt = getopt_long(argc, argv, str_options, long_options, &long_index )) != -1) {
switch(opt) {
case 'c':
opt_chapter_number = true;
token = strtok(optarg, "-"); {
if(strlen(token) > 2) {
fprintf(stderr, "Chapter range must be between 1 and 99\n");
return 1;
}
arg_first_chapter = (uint8_t)strtoumax(token, NULL, 0);
}
token = strtok(NULL, "-");
if(token != NULL) {
if(strlen(token) > 2) {
fprintf(stderr, "Chapter range must be between 1 and 99\n");
return 1;
}
arg_last_chapter = (uint8_t)strtoumax(token, NULL, 0);
}
if(arg_first_chapter == 0)
arg_first_chapter = 1;
if(arg_last_chapter < arg_first_chapter)
arg_last_chapter = arg_first_chapter;
break;
case 'h':
print_usage("dvd_copy");
return 0;
case 't':
opt_track_number = true;
arg_track_number = (uint16_t)strtoumax(optarg, NULL, 0);
break;
// ignore unknown arguments
case '?':
print_usage("dvd_copy");
return 1;
// let getopt_long set the variable
case 0:
default:
break;
}
}
const char *device_filename = DEFAULT_DVD_DEVICE;
if (argv[optind])
device_filename = argv[optind];
if(access(device_filename, F_OK) != 0) {
fprintf(stderr, "cannot access %s\n", device_filename);
return 1;
}
// Check to see if device can be opened
int dvd_fd = 0;
dvd_fd = dvd_device_open(device_filename);
if(dvd_fd < 0) {
fprintf(stderr, "dvd_copy: error opening %s\n", device_filename);
return 1;
}
dvd_device_close(dvd_fd);
#ifdef __linux__
// Poll drive status if it is hardware
if(dvd_device_is_hardware(device_filename)) {
// Wait for the drive to become ready
if(!dvd_drive_has_media(device_filename)) {
fprintf(stderr, "drive status: ");
dvd_drive_display_status(device_filename);
return 1;
}
}
#endif
dvd_reader_t *dvdread_dvd = NULL;
dvdread_dvd = DVDOpen(device_filename);
if(!dvdread_dvd) {
printf("* dvdread could not open %s\n", device_filename);
return 1;
}
ifo_handle_t *vmg_ifo = NULL;
vmg_ifo = ifoOpen(dvdread_dvd, 0);
if(vmg_ifo == NULL) {
printf("* Could not open IFO zero\n");
DVDClose(dvdread_dvd);
return 1;
}
printf("Disc Title: %s\n", dvd_title(device_filename));
uint16_t num_ifos = 1;
num_ifos = vmg_ifo->vts_atrt->nr_of_vtss;
if(num_ifos < 1) {
printf("* DVD has no title IFOs?!\n");
printf("* Most likely a bug in libdvdread or a bad master or problems reading the disc\n");
ifoClose(vmg_ifo);
DVDClose(dvdread_dvd);
return 1;
}
// DVD
struct dvd_info dvd_info;
memset(&dvd_info, 0, sizeof(dvd_info));
dvd_info.tracks = dvd_tracks(vmg_ifo);
dvd_info.longest_track = 1;
dvd_info.video_title_sets = dvd_video_title_sets(vmg_ifo);
dvd_info.tracks = dvd_tracks(vmg_ifo);
// Track
struct dvd_track dvd_track;
memset(&dvd_track, 0, sizeof(dvd_track));
struct dvd_track dvd_tracks[DVD_MAX_TRACKS];
memset(&dvd_tracks, 0, sizeof(dvd_track) * dvd_info.tracks);
// Cells
struct dvd_cell dvd_cell;
dvd_cell.cell = 1;
memset(dvd_cell.length, '\0', sizeof(dvd_cell.length));
snprintf(dvd_cell.length, DVD_CELL_LENGTH + 1, "00:00:00.000");
dvd_cell.msecs = 0;
// Open first IFO
uint16_t vts = 1;
ifo_handle_t *vts_ifo = NULL;
vts_ifo = ifoOpen(dvdread_dvd, vts);
if(vts_ifo == NULL) {
printf("* Could not open VTS_IFO for track %u\n", 1);
return 1;
}
ifoClose(vts_ifo);
vts_ifo = NULL;
// Create an array of all the IFOs
ifo_handle_t *vts_ifos[DVD_MAX_VTS_IFOS];
vts_ifos[0] = NULL;
for(vts = 1; vts < dvd_info.video_title_sets + 1; vts++) {
vts_ifos[vts] = ifoOpen(dvdread_dvd, vts);
if(!vts_ifos[vts]) {
vts_ifos[vts] = NULL;
} else if(!ifo_is_vts(vts_ifos[vts])) {
ifoClose(vts_ifos[vts]);
vts_ifos[vts] = NULL;
}
}
// Exit if track number requested does not exist
if(opt_track_number && (arg_track_number > dvd_info.tracks)) {
fprintf(stderr, "dvd_copy: Invalid track number %d\n", arg_track_number);
fprintf(stderr, "dvd_copy: Valid track numbers: 1 to %u\n", dvd_info.tracks);
ifoClose(vmg_ifo);
DVDClose(dvdread_dvd);
return 1;
} else if(opt_track_number) {
dvd_copy.track = arg_track_number;
}
uint16_t ix = 0;
uint16_t track = 1;
uint32_t longest_msecs = 0;
for(ix = 0, track = 1; ix < dvd_info.tracks; ix++, track++) {
vts = dvd_vts_ifo_number(vmg_ifo, ix + 1);
vts_ifo = vts_ifos[vts];
dvd_track_info(&dvd_tracks[ix], track, vmg_ifo, vts_ifo);
if(dvd_tracks[ix].msecs > longest_msecs) {
dvd_info.longest_track = track;
longest_msecs = dvd_tracks[ix].msecs;
}
}
// Set the track number to rip if none is passed as an argument
if(!opt_track_number)
dvd_copy.track = dvd_info.longest_track;
dvd_track = dvd_tracks[dvd_copy.track - 1];
// Set the proper chapter range
if(opt_chapter_number) {
if(arg_first_chapter > dvd_track.chapters) {
dvd_copy.first_chapter = dvd_track.chapters;
fprintf(stderr, "Resetting first chapter to %u\n", dvd_copy.first_chapter);
} else
dvd_copy.first_chapter = arg_first_chapter;
if(arg_last_chapter > dvd_track.chapters) {
dvd_copy.last_chapter = dvd_track.chapters;
fprintf(stderr, "Resetting last chapter to %u\n", dvd_copy.last_chapter);
} else
dvd_copy.last_chapter = arg_last_chapter;
} else {
dvd_copy.first_chapter = 1;
dvd_copy.last_chapter = dvd_track.chapters;
}
/**
* Integers for numbers of blocks read, copied, counters
*/
int offset = 0;
ssize_t read_blocks = DVD_READ_BLOCKS;
ssize_t dvdread_read_blocks = 0; // num blocks passed by dvdread function
ssize_t cell_blocks_written = 0;
ssize_t dvd_blocks_written = 0;
ssize_t bytes_written = 0;
uint32_t cell_sectors = 0;
/**
* File descriptors and filenames
*/
dvd_file_t *dvdread_vts_file = NULL;
vts = dvd_vts_ifo_number(vmg_ifo, dvd_copy.track);
vts_ifo = vts_ifos[vts];
// Open the VTS VOB
dvdread_vts_file = DVDOpenFile(dvdread_dvd, vts, DVD_READ_TITLE_VOBS);
printf("Track: %02u, Length: %s Chapters: %02u, Cells: %02u, Audio streams: %02u, Subpictures: %02u, Filesize: %lu, Blocks: %lu\n", dvd_track.track, dvd_track.length, dvd_track.chapters, dvd_track.cells, dvd_track.audio_tracks, dvd_track.subtitles, dvd_track.filesize, dvd_track.blocks);
dvd_copy.first_cell = dvd_chapter_first_cell(vmg_ifo, vts_ifo, dvd_copy.track, dvd_copy.first_chapter);
dvd_copy.last_cell = dvd_chapter_last_cell(vmg_ifo, vts_ifo, dvd_copy.track, dvd_copy.last_chapter);
// Get limits of copy
for(dvd_cell.cell = dvd_copy.first_cell; dvd_cell.cell < dvd_copy.last_cell + 1; dvd_cell.cell++) {
dvd_copy.blocks += dvd_cell_blocks(vmg_ifo, vts_ifo, dvd_track.track, dvd_cell.cell);
dvd_copy.filesize += dvd_cell_filesize(vmg_ifo, vts_ifo, dvd_track.track, dvd_cell.cell);
}
uint8_t *end = NULL;
uint8_t dvd_read_buffer[DVD_BUFFER_SIZE];
struct dvd_chapter dvd_chapter;
unsigned long progress = 0;
unsigned long last_pass = 0;
for(dvd_chapter.chapter = dvd_copy.first_chapter; dvd_chapter.chapter < dvd_copy.last_chapter + 1; dvd_chapter.chapter++) {
dvd_chapter.first_cell = dvd_chapter_first_cell(vmg_ifo, vts_ifo, dvd_copy.track, dvd_chapter.chapter);
dvd_chapter.last_cell = dvd_chapter_last_cell(vmg_ifo, vts_ifo, dvd_copy.track, dvd_chapter.chapter);
strncpy(dvd_chapter.length, dvd_chapter_length(vmg_ifo, vts_ifo, dvd_copy.track, dvd_chapter.chapter), DVD_CHAPTER_LENGTH);
for(dvd_cell.cell = dvd_chapter.first_cell; dvd_cell.cell < dvd_chapter.last_cell + 1; dvd_cell.cell++) {
dvd_cell.blocks = dvd_cell_blocks(vmg_ifo, vts_ifo, dvd_track.track, dvd_cell.cell);
dvd_cell.filesize = dvd_cell_filesize(vmg_ifo, vts_ifo, dvd_track.track, dvd_cell.cell);
dvd_cell.first_sector = dvd_cell_first_sector(vmg_ifo, vts_ifo, dvd_track.track, dvd_cell.cell);
dvd_cell.last_sector = dvd_cell_last_sector(vmg_ifo, vts_ifo, dvd_track.track, dvd_cell.cell);
strncpy(dvd_cell.length, dvd_cell_length(vmg_ifo, vts_ifo, dvd_track.track, dvd_cell.cell), DVD_CELL_LENGTH);
cell_sectors = dvd_cell.last_sector - dvd_cell.first_sector;
printf(" Chapter: %02u, Cell: %02u, VTS: %u, Filesize: %lu, Blocks: %lu, Sectors: %i to %i\n", dvd_chapter.chapter, dvd_cell.cell, vts, dvd_cell.filesize, dvd_cell.blocks, dvd_cell.first_sector, dvd_cell.last_sector);
cell_blocks_written = 0;
if(dvd_cell.last_sector > dvd_cell.first_sector)
cell_sectors++;
if(dvd_cell.last_sector < dvd_cell.first_sector) {
printf("* DEBUG Someone doing something nasty? The last sector is listed before the first; skipping cell\n");
continue;
}
offset = (int)dvd_cell.first_sector;
// This is where you would change the boundaries -- are you dumping to a track file (no boundaries) or a VOB (boundaries)
while(cell_blocks_written < dvd_cell.blocks) {
// libmpeg2
do {
// Limit blocks to be read based on how close we are to finishing
if(read_blocks > (dvd_cell.blocks - cell_blocks_written)) {
read_blocks = dvd_cell.blocks - cell_blocks_written;
}
dvdread_read_blocks = DVDReadBlocks(dvdread_vts_file, offset, (uint64_t)read_blocks, dvd_read_buffer);
if(!dvdread_read_blocks) {
printf("* Could not read data from cell %u\n", dvd_cell.cell);
return 1;
}
// Check to make sure the amount read was what we wanted
if(dvdread_read_blocks != read_blocks) {
printf("*** Asked for %ld and only got %ld\n", read_blocks, dvdread_read_blocks);
return 1;
}
offset += dvdread_read_blocks;
bytes_written = read_blocks * DVD_VIDEO_LB_LEN;
end = dvd_read_buffer + bytes_written;
if(demux(dvd_read_buffer, end, 0)) {
break;
}
cell_blocks_written += dvdread_read_blocks;
dvd_blocks_written += dvdread_read_blocks;
progress = dvd_blocks_written * 100 / dvd_copy.blocks;
if(progress > last_pass) {
printf("Progress: %lu%%\r", progress);
fflush(stdout);
}
last_pass = progress;
} while(dvdread_read_blocks && cell_blocks_written < dvd_cell.blocks);
}
}
}
if(mpeg_file)
fclose(mpeg_file);
DVDCloseFile(dvdread_vts_file);
if(vts_ifo)
ifoClose(vts_ifo);
if(vmg_ifo)
ifoClose(vmg_ifo);
if(dvdread_dvd)
DVDClose(dvdread_dvd);
return 0;
}
void dvd_track_info(struct dvd_track *dvd_track, const uint16_t track_number, const ifo_handle_t *vmg_ifo, const ifo_handle_t *vts_ifo) {
dvd_track->track = track_number;
dvd_track->valid = 1;
dvd_track->vts = dvd_vts_ifo_number(vmg_ifo, track_number);
dvd_track->ttn = dvd_track_ttn(vmg_ifo, track_number);
strncpy(dvd_track->length, dvd_track_length(vmg_ifo, vts_ifo, track_number), DVD_TRACK_LENGTH);
dvd_track->msecs = dvd_track_msecs(vmg_ifo, vts_ifo, track_number);
dvd_track->chapters = dvd_track_chapters(vmg_ifo, vts_ifo, track_number);
dvd_track->audio_tracks = dvd_track_audio_tracks(vts_ifo);
dvd_track->subtitles = dvd_track_subtitles(vts_ifo);
dvd_track->active_audio = dvd_audio_active_tracks(vmg_ifo, vts_ifo, track_number);
dvd_track->active_subs = dvd_track_active_subtitles(vmg_ifo, vts_ifo, track_number);
dvd_track->cells = dvd_track_cells(vmg_ifo, vts_ifo, track_number);
dvd_track->blocks = dvd_track_blocks(vmg_ifo, vts_ifo, track_number);
dvd_track->filesize = dvd_track_filesize(vmg_ifo, vts_ifo, track_number);
}
void print_usage(char *binary) {
printf("%s - Copy a DVD track\n", binary);
printf("\n");
printf("Usage: %s [-t track] [dvd path]\n", binary);
printf("\n");
printf("DVD path can be a directory, a device filename, or a local file.\n");
printf("\n");
printf("Examples:\n");
printf(" dvd_info " DEFAULT_DVD_DEVICE " # Read a DVD drive directly\n");
printf(" dvd_info movie.iso # Read an image file\n");
printf(" dvd_info movie/ # Read a directory that contains VIDEO_TS\n");
printf("\n");
printf("If no DVD path is given, %s is used in its place.\n", DEFAULT_DVD_DEVICE);
}
static int demux(uint8_t *buf, uint8_t *end, int flags) {
#define DEMUX_HEADER 0
#define DEMUX_DATA 1
#define DEMUX_SKIP 2
static int mpeg1_skip_table[16] = {
0, 0, 4, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
static int state = DEMUX_SKIP;
static int state_bytes = 0;
static uint8_t head_buf[264];
uint8_t *header = NULL;
int bytes = 0;
int len = 0;
size_t writeval = 0;
#define NEEDBYTES(x) \
do { \
int missing = 0; \
missing = (x) - bytes; \
if(missing > 0) { \
if (header == head_buf) { \
if(missing <= end - buf) { \
memcpy(header + bytes, buf, missing); \
buf += missing; \
bytes = (x); \
} else { \
memcpy(header + bytes, buf, end - buf); \
state_bytes = bytes + end - buf; \
return 0; \
} \
} else { \
memcpy (head_buf, header, bytes); \
state = DEMUX_HEADER; \
state_bytes = bytes; \
return 0; \
} \
} \
} while (0)
#define DONEBYTES(x) \
do { \
if (header != head_buf) \
buf = header + (x); \
} while (0)
if(flags & DEMUX_PAYLOAD_START)
goto payload_start;
switch (state) {
case DEMUX_HEADER:
if(state_bytes > 0) {
header = head_buf;
bytes = state_bytes;
goto continue_header;
}
break;
case DEMUX_DATA:
if(demux_pid || (state_bytes > end - buf)) {
writeval = fwrite(buf, end - buf, 1, mpeg_file);
if(!writeval) {
printf("Could not write to %s\n", mpeg_filename);
return 1;
}
state_bytes -= end - buf;
return 0;
}
writeval = fwrite(buf, state_bytes, 1, mpeg_file);
if(!writeval) {
printf("Could not write to %s\n", mpeg_filename);
return 1;
}
buf += state_bytes;
break;
case DEMUX_SKIP:
if(demux_pid || (state_bytes > end - buf)) {
state_bytes -= end - buf;
return 0;
}
buf += state_bytes;
break;
}
while(true) {
if(demux_pid) {
state = DEMUX_SKIP;
return 0;
}
payload_start:
header = buf;
bytes = end - buf;
continue_header:
NEEDBYTES (4);
if(header[0] || header[1] || (header[2] != 1)) {
if (demux_pid) {
state = DEMUX_SKIP;
return 0;
} else if(header != head_buf) {
buf++;
goto payload_start;
} else {
header[0] = header[1];
header[1] = header[2];
header[2] = header[3];
bytes = 3;
goto continue_header;
}
}
if(demux_pid) {
if((header[3] >= 0xe0) && (header[3] <= 0xef))
goto pes;
fprintf(stderr, "bad stream id %x\n", header[3]);
exit(1);
}
switch (header[3]) {
/* program end code */
case 0xb9:
/* DONEBYTES (4); */
/* break; */
return 1;
/* pack header */
case 0xba:
NEEDBYTES (5);
if((header[4] & 0xc0) == 0x40) { /* mpeg2 */
NEEDBYTES (14);
len = 14 + (header[13] & 7);
NEEDBYTES (len);
DONEBYTES (len);
/* header points to the mpeg2 pack header */
} else if((header[4] & 0xf0) == 0x20) { /* mpeg1 */
NEEDBYTES (12);
DONEBYTES (12);
/* header points to the mpeg1 pack header */
} else {
fprintf (stderr, "weird pack header\n");
DONEBYTES (5);
}
break;
default:
if (header[3] == demux_track) {
pes:
NEEDBYTES (7);
if((header[6] & 0xc0) == 0x80) { /* mpeg2 */
NEEDBYTES (9);
len = 9 + header[8];
NEEDBYTES (len);
/* header points to the mpeg2 pes header */
} else { /* mpeg1 */
len = 7;
while((header-1)[len] == 0xff) {
len++;
NEEDBYTES (len);
if(len > 23) {
fprintf(stderr, "too much stuffing\n");
break;
}
}
if(((header-1)[len] & 0xc0) == 0x40) {
len += 2;
NEEDBYTES (len);
}
len += mpeg1_skip_table[(header - 1)[len] >> 4];
NEEDBYTES (len);
/* header points to the mpeg1 pes header */
}
DONEBYTES (len);
bytes = 6 + (header[4] << 8) + header[5] - len;
if(demux_pid || (bytes > end - buf)) {
writeval = fwrite(buf, end - buf, 1, mpeg_file);
if(!writeval) {
printf("Could not write to %s\n", mpeg_filename);
return 1;
}
state = DEMUX_DATA;
state_bytes = bytes - (end - buf);
return 0;
} else if(bytes <= 0) {
continue;
}
writeval = fwrite(buf, bytes, 1, mpeg_file);
if(!writeval) {
printf("Could not write to %s\n", mpeg_filename);
return 1;
}
buf += bytes;
} else if(header[3] < 0xb9) {
fprintf (stderr, "looks like a video stream, not system stream\n");
DONEBYTES (4);
} else {
NEEDBYTES (6);
DONEBYTES (6);
bytes = (header[4] << 8) + header[5];
if(bytes > end - buf) {
state = DEMUX_SKIP;
state_bytes = bytes - (end - buf);
return 0;
}
buf += bytes;
}
}
}
}