diff --git a/.gitignore b/.gitignore index f8b73e7..b676e85 100644 --- a/.gitignore +++ b/.gitignore @@ -138,3 +138,13 @@ dmypy.json # Cython debug symbols cython_debug/ + +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ diff --git a/README.md b/README.md index ec65bc8..d8081f4 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,27 @@ Choggbuster is a set of python scripts aimed at automated preprocessing of video DVDs for archival and filtering +# Requirements + +- python (obviously) +- libdvdcss (for decrypting copy protected DVDs) +- libdvdnav (for streaming the VOBs to disk) +- libdvdread (for reading decrypted data off of DVDs) +- ffmpeg (for demuxing) +- ccextractor (for extracting DVD Subtitles) + # Setup (Windows) 1. Clone the repo 2. `pip install cffi tqdm` 3. Grab [libdvdread, libdvdnav](https://www.videolan.org/developers/libdvdnav.html) and [libdvdcss](https://www.videolan.org/developers/libdvdcss.html) from VLC and drop them next to `dvd_ripper.py` -4. `python dvd_ripper.py F:\` or `python dvd_ripper.py D:\path\to\DVD.ISO` \ No newline at end of file +4. `python dvd_ripper.py F:\` or `python dvd_ripper.py D:\path\to\DVD.ISO` +5. this will create a folder `out` with a subfolder for the disc containing: + - JSON file with metadata for the DVD title (`XXXX.json` where `X` is the title number) + - demuxed streams (`tXXX_aYYY_Z_0xAAA.{ext}` where `X` is the title number, `Y` is the angle number `Z` is stream index and `AAA` is the stream id) + - `.m2v` for video + - `.ac3` or `.dtx` for audio + - `.sub` and `.idx` for subtitles + - `.srt` for captions + - `.d2v` file for use with AviSynth and Vapoursynth D2V reader + - `.info.json` file containing video stream metadata (cropping information, interlaced/progressive frame count and aspect ration information) diff --git a/dvd_reader.h b/dvd_reader.h index 9a99ed9..8ac1a5e 100644 --- a/dvd_reader.h +++ b/dvd_reader.h @@ -1,344 +1,344 @@ -/* - * Copyright (C) 2001, 2002 Billy Biggs , - * Håkan Hjort , - * Björn Englund - * - * This file is part of libdvdread. - * - * libdvdread 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 of the License, or - * (at your option) any later version. - * - * libdvdread 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 libdvdread; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * The DVD access interface. - * - * This file contains the functions that form the interface to to - * reading files located on a DVD. - */ - -/** - * The current version. - */ - -/** - * The length of one Logical Block of a DVD. - */ -#define DVD_VIDEO_LB_LEN 2048 - -/** - * Maximum length of filenames allowed in UDF. - */ -#define MAX_UDF_FILE_NAME_LEN 2048 - -typedef long int off_t; - -/** - * Opaque type that is used as a handle for one instance of an opened DVD. - */ -typedef struct dvd_reader_s dvd_reader_t; -typedef struct dvd_reader_device_s dvd_reader_device_t; - -/** - * Opaque type for a file read handle, much like a normal fd or FILE *. - */ -typedef struct dvd_file_s dvd_file_t; - -struct dvd_reader_stream_cb -{ - int ( *pf_seek ) ( void *p_stream, uint64_t i_pos); - int ( *pf_read ) ( void *p_stream, void* buffer, int i_read); - int ( *pf_readv ) ( void *p_stream, void *p_iovec, int i_blocks); -}; -typedef struct dvd_reader_stream_cb dvd_reader_stream_cb; - -/** - * Custom logger callback for DVDOpen[Stream]2 - * @param private Handle as provided in Open functions - * @param level Log level - * @param fmt Format string - * @param args Arguments list - * pf_log(priv, level, fmt, args); - */ -typedef enum -{ - DVD_LOGGER_LEVEL_INFO, - DVD_LOGGER_LEVEL_ERROR, - DVD_LOGGER_LEVEL_WARN, - DVD_LOGGER_LEVEL_DEBUG, -} dvd_logger_level_t; - -typedef struct -{ - // void ( *pf_log ) ( void *, dvd_logger_level_t, const char *, va_list ); - void *pf_log; -} dvd_logger_cb; - -/** - * Public type that is used to provide statistics on a handle. - */ -typedef struct { - off_t size; /**< Total size of file in bytes */ - int nr_parts; /**< Number of file parts */ - off_t parts_size[9]; /**< Size of each part in bytes */ -} dvd_stat_t; - -/** - * Opens a block device of a DVD-ROM file, or an image file, or a directory - * name for a mounted DVD or HD copy of a DVD. - * The second form of Open function (DVDOpenStream) can be used to - * provide custom stream_cb functions to access the DVD (see libdvdcss). - * - * If the given file is a block device, or is the mountpoint for a block - * device, then that device is used for CSS authentication using libdvdcss. - * If no device is available, then no CSS authentication is performed, - * and we hope that the image is decrypted. - * - * If the path given is a directory, then the files in that directory may be - * in any one of these formats: - * - * path/VIDEO_TS/VTS_01_1.VOB - * path/video_ts/vts_01_1.vob - * path/VTS_01_1.VOB - * path/vts_01_1.vob - * - * @param path Specifies the the device, file or directory to be used. - * @param stream is a private handle used by stream_cb - * @param stream_cb is a struct containing seek and read functions - * @return If successful a a read handle is returned. Otherwise 0 is returned. - * - * dvd = DVDOpen(path); - * dvd = DVDOpenStream(stream, &stream_cb); - */ -dvd_reader_t *DVDOpen( const char * ); -dvd_reader_t *DVDOpenStream( void *, dvd_reader_stream_cb * ); - -/** - * Same as DVDOpen, but with private handle to be passed back on callbacks - * - * @param path Specifies the the device, file or directory to be used. - * @param priv is a private handle - * @param logcb is a custom logger callback struct, or NULL if none needed - * @param stream_cb is a struct containing seek and read functions - * @return If successful a a read handle is returned. Otherwise 0 is returned. - * - * dvd = DVDOpen2(priv, logcb, path); - * dvd = DVDOpenStream2(priv, logcb, &stream_cb); - */ -dvd_reader_t *DVDOpen2( void *, const dvd_logger_cb *, const char * ); -dvd_reader_t *DVDOpenStream2( void *, const dvd_logger_cb *, dvd_reader_stream_cb * ); - -/** - * Closes and cleans up the DVD reader object. - * - * You must close all open files before calling this function. - * - * @param dvd A read handle that should be closed. - * - * DVDClose(dvd); - */ -void DVDClose( dvd_reader_t * ); - -/** - * - */ -typedef enum { - DVD_READ_INFO_FILE, /**< VIDEO_TS.IFO or VTS_XX_0.IFO (title) */ - DVD_READ_INFO_BACKUP_FILE, /**< VIDEO_TS.BUP or VTS_XX_0.BUP (title) */ - DVD_READ_MENU_VOBS, /**< VIDEO_TS.VOB or VTS_XX_0.VOB (title) */ - DVD_READ_TITLE_VOBS /**< VTS_XX_[1-9].VOB (title). All files in - the title set are opened and read as a - single file. */ -} dvd_read_domain_t; - -/** - * Stats a file on the DVD given the title number and domain. - * The information about the file is stored in a dvd_stat_t - * which contains information about the size of the file and - * the number of parts in case of a multipart file and the respective - * sizes of the parts. - * A multipart file is for instance VTS_02_1.VOB, VTS_02_2.VOB, VTS_02_3.VOB - * The size of VTS_02_1.VOB will be stored in stat->parts_size[0], - * VTS_02_2.VOB in stat->parts_size[1], ... - * The total size (sum of all parts) is stored in stat->size and - * stat->nr_parts will hold the number of parts. - * Only DVD_READ_TITLE_VOBS (VTS_??_[1-9].VOB) can be multipart files. - * - * This function is only of use if you want to get the size of each file - * in the filesystem. These sizes are not needed to use any other - * functions in libdvdread. - * - * @param dvd A dvd read handle. - * @param titlenum Which Video Title Set should be used, VIDEO_TS is 0. - * @param domain Which domain. - * @param stat Pointer to where the result is stored. - * @return If successful 0, otherwise -1. - * - * int DVDFileStat(dvd, titlenum, domain, stat); - */ -int DVDFileStat(dvd_reader_t *, int, dvd_read_domain_t, dvd_stat_t *); - -/** - * Opens a file on the DVD given the title number and domain. - * - * If the title number is 0, the video manager information is opened - * (VIDEO_TS.[IFO,BUP,VOB]). Returns a file structure which may be - * used for reads, or 0 if the file was not found. - * - * @param dvd A dvd read handle. - * @param titlenum Which Video Title Set should be used, VIDEO_TS is 0. - * @param domain Which domain. - * @return If successful a a file read handle is returned, otherwise 0. - * - * dvd_file = DVDOpenFile(dvd, titlenum, domain); */ -dvd_file_t *DVDOpenFile( dvd_reader_t *, int, dvd_read_domain_t ); - -/** - * Closes a file and frees the associated structure. - * - * @param dvd_file The file read handle to be closed. - * - * DVDCloseFile(dvd_file); - */ -void DVDCloseFile( dvd_file_t * ); - -/** - * Reads block_count number of blocks from the file at the given block offset. - * Returns number of blocks read on success, -1 on error. This call is only - * for reading VOB data, and should not be used when reading the IFO files. - * When reading from an encrypted drive, blocks are decrypted using libdvdcss - * where required. - * - * @param dvd_file A file read handle. - * @param offset Block offset from the start of the file to start reading at. - * @param block_count Number of block to read. - * @param data Pointer to a buffer to write the data into. - * @return Returns number of blocks read on success, -1 on error. - * - * blocks_read = DVDReadBlocks(dvd_file, offset, block_count, data); - */ -ssize_t DVDReadBlocks( dvd_file_t *, int, size_t, unsigned char * ); - -/** - * Seek to the given position in the file. Returns the resulting position in - * bytes from the beginning of the file. The seek position is only used for - * byte reads from the file, the block read call always reads from the given - * offset. - * - * @param dvd_file A file read handle. - * @param seek_offset Byte offset from the start of the file to seek to. - * @return The resulting position in bytes from the beginning of the file. - * - * offset_set = DVDFileSeek(dvd_file, seek_offset); - */ -int32_t DVDFileSeek( dvd_file_t *, int32_t ); - -/** - * Reads the given number of bytes from the file. This call can only be used - * on the information files, and may not be used for reading from a VOB. This - * reads from and increments the currrent seek position for the file. - * - * @param dvd_file A file read handle. - * @param data Pointer to a buffer to write the data into. - * @param bytes Number of bytes to read. - * @return Returns number of bytes read on success, -1 on error. - * - * bytes_read = DVDReadBytes(dvd_file, data, bytes); - */ -ssize_t DVDReadBytes( dvd_file_t *, void *, size_t ); - -/** - * Returns the file size in blocks. - * - * @param dvd_file A file read handle. - * @return The size of the file in blocks, -1 on error. - * - * blocks = DVDFileSize(dvd_file); - */ -ssize_t DVDFileSize( dvd_file_t * ); - -/** - * Get a unique 128 bit disc ID. - * This is the MD5 sum of VIDEO_TS.IFO and the VTS_0?_0.IFO files - * in title order (those that exist). - * If you need a 'text' representation of the id, print it as a - * hexadecimal number, using lowercase letters, discid[0] first. - * I.e. the same format as the command-line 'md5sum' program uses. - * - * @param dvd A read handle to get the disc ID from - * @param discid The buffer to put the disc ID into. The buffer must - * have room for 128 bits (16 chars). - * @return 0 on success, -1 on error. - */ -int DVDDiscID( dvd_reader_t *, unsigned char * ); - -/** - * Get the UDF VolumeIdentifier and VolumeSetIdentifier - * from the PrimaryVolumeDescriptor. - * - * @param dvd A read handle to get the disc ID from - * @param volid The buffer to put the VolumeIdentifier into. - * The VolumeIdentifier is latin-1 encoded (8bit unicode) - * null terminated and max 32 bytes (including '\0') - * @param volid_size No more than volid_size bytes will be copied to volid. - * If the VolumeIdentifier is truncated because of this - * it will still be null terminated. - * @param volsetid The buffer to put the VolumeSetIdentifier into. - * The VolumeIdentifier is 128 bytes as - * stored in the UDF PrimaryVolumeDescriptor. - * Note that this is not a null terminated string. - * @param volsetid_size At most volsetid_size bytes will be copied to volsetid. - * @return 0 on success, -1 on error. - */ -int DVDUDFVolumeInfo( dvd_reader_t *, char *, unsigned int, - unsigned char *, unsigned int ); - -int DVDFileSeekForce( dvd_file_t *, int offset, int force_size); - -/** - * Get the ISO9660 VolumeIdentifier and VolumeSetIdentifier - * - * * Only use this function as fallback if DVDUDFVolumeInfo returns 0 * - * * this will happen on a disc mastered only with a iso9660 filesystem * - * * All video DVD discs have UDF filesystem * - * - * @param dvd A read handle to get the disc ID from - * @param volid The buffer to put the VolumeIdentifier into. - * The VolumeIdentifier is coded with '0-9','A-Z','_' - * null terminated and max 33 bytes (including '\0') - * @param volid_size No more than volid_size bytes will be copied to volid. - * If the VolumeIdentifier is truncated because of this - * it will still be null terminated. - * @param volsetid The buffer to put the VolumeSetIdentifier into. - * The VolumeIdentifier is 128 bytes as - * stored in the ISO9660 PrimaryVolumeDescriptor. - * Note that this is not a null terminated string. - * @param volsetid_size At most volsetid_size bytes will be copied to volsetid. - * @return 0 on success, -1 on error. - */ -int DVDISOVolumeInfo( dvd_reader_t *, char *, unsigned int, - unsigned char *, unsigned int ); - -/** - * Sets the level of caching that is done when reading from a device - * - * @param dvd A read handle to get the disc ID from - * @param level The level of caching wanted. - * -1 - returns the current setting. - * 0 - UDF Cache turned off. - * 1 - (default level) Pointers to IFO files and some data from - * PrimaryVolumeDescriptor are cached. - * - * @return The level of caching. - */ -int DVDUDFCacheLevel( dvd_reader_t *, int ); +/* + * Copyright (C) 2001, 2002 Billy Biggs , + * Håkan Hjort , + * Björn Englund + * + * This file is part of libdvdread. + * + * libdvdread 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 of the License, or + * (at your option) any later version. + * + * libdvdread 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 libdvdread; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * The DVD access interface. + * + * This file contains the functions that form the interface to to + * reading files located on a DVD. + */ + +/** + * The current version. + */ + +/** + * The length of one Logical Block of a DVD. + */ +#define DVD_VIDEO_LB_LEN 2048 + +/** + * Maximum length of filenames allowed in UDF. + */ +#define MAX_UDF_FILE_NAME_LEN 2048 + +typedef long int off_t; + +/** + * Opaque type that is used as a handle for one instance of an opened DVD. + */ +typedef struct dvd_reader_s dvd_reader_t; +typedef struct dvd_reader_device_s dvd_reader_device_t; + +/** + * Opaque type for a file read handle, much like a normal fd or FILE *. + */ +typedef struct dvd_file_s dvd_file_t; + +struct dvd_reader_stream_cb +{ + int ( *pf_seek ) ( void *p_stream, uint64_t i_pos); + int ( *pf_read ) ( void *p_stream, void* buffer, int i_read); + int ( *pf_readv ) ( void *p_stream, void *p_iovec, int i_blocks); +}; +typedef struct dvd_reader_stream_cb dvd_reader_stream_cb; + +/** + * Custom logger callback for DVDOpen[Stream]2 + * @param private Handle as provided in Open functions + * @param level Log level + * @param fmt Format string + * @param args Arguments list + * pf_log(priv, level, fmt, args); + */ +typedef enum +{ + DVD_LOGGER_LEVEL_INFO, + DVD_LOGGER_LEVEL_ERROR, + DVD_LOGGER_LEVEL_WARN, + DVD_LOGGER_LEVEL_DEBUG, +} dvd_logger_level_t; + +typedef struct +{ + // void ( *pf_log ) ( void *, dvd_logger_level_t, const char *, va_list ); + void *pf_log; +} dvd_logger_cb; + +/** + * Public type that is used to provide statistics on a handle. + */ +typedef struct { + off_t size; /**< Total size of file in bytes */ + int nr_parts; /**< Number of file parts */ + off_t parts_size[9]; /**< Size of each part in bytes */ +} dvd_stat_t; + +/** + * Opens a block device of a DVD-ROM file, or an image file, or a directory + * name for a mounted DVD or HD copy of a DVD. + * The second form of Open function (DVDOpenStream) can be used to + * provide custom stream_cb functions to access the DVD (see libdvdcss). + * + * If the given file is a block device, or is the mountpoint for a block + * device, then that device is used for CSS authentication using libdvdcss. + * If no device is available, then no CSS authentication is performed, + * and we hope that the image is decrypted. + * + * If the path given is a directory, then the files in that directory may be + * in any one of these formats: + * + * path/VIDEO_TS/VTS_01_1.VOB + * path/video_ts/vts_01_1.vob + * path/VTS_01_1.VOB + * path/vts_01_1.vob + * + * @param path Specifies the the device, file or directory to be used. + * @param stream is a private handle used by stream_cb + * @param stream_cb is a struct containing seek and read functions + * @return If successful a a read handle is returned. Otherwise 0 is returned. + * + * dvd = DVDOpen(path); + * dvd = DVDOpenStream(stream, &stream_cb); + */ +dvd_reader_t *DVDOpen( const char * ); +dvd_reader_t *DVDOpenStream( void *, dvd_reader_stream_cb * ); + +/** + * Same as DVDOpen, but with private handle to be passed back on callbacks + * + * @param path Specifies the the device, file or directory to be used. + * @param priv is a private handle + * @param logcb is a custom logger callback struct, or NULL if none needed + * @param stream_cb is a struct containing seek and read functions + * @return If successful a a read handle is returned. Otherwise 0 is returned. + * + * dvd = DVDOpen2(priv, logcb, path); + * dvd = DVDOpenStream2(priv, logcb, &stream_cb); + */ +dvd_reader_t *DVDOpen2( void *, const dvd_logger_cb *, const char * ); +dvd_reader_t *DVDOpenStream2( void *, const dvd_logger_cb *, dvd_reader_stream_cb * ); + +/** + * Closes and cleans up the DVD reader object. + * + * You must close all open files before calling this function. + * + * @param dvd A read handle that should be closed. + * + * DVDClose(dvd); + */ +void DVDClose( dvd_reader_t * ); + +/** + * + */ +typedef enum { + DVD_READ_INFO_FILE, /**< VIDEO_TS.IFO or VTS_XX_0.IFO (title) */ + DVD_READ_INFO_BACKUP_FILE, /**< VIDEO_TS.BUP or VTS_XX_0.BUP (title) */ + DVD_READ_MENU_VOBS, /**< VIDEO_TS.VOB or VTS_XX_0.VOB (title) */ + DVD_READ_TITLE_VOBS /**< VTS_XX_[1-9].VOB (title). All files in + the title set are opened and read as a + single file. */ +} dvd_read_domain_t; + +/** + * Stats a file on the DVD given the title number and domain. + * The information about the file is stored in a dvd_stat_t + * which contains information about the size of the file and + * the number of parts in case of a multipart file and the respective + * sizes of the parts. + * A multipart file is for instance VTS_02_1.VOB, VTS_02_2.VOB, VTS_02_3.VOB + * The size of VTS_02_1.VOB will be stored in stat->parts_size[0], + * VTS_02_2.VOB in stat->parts_size[1], ... + * The total size (sum of all parts) is stored in stat->size and + * stat->nr_parts will hold the number of parts. + * Only DVD_READ_TITLE_VOBS (VTS_??_[1-9].VOB) can be multipart files. + * + * This function is only of use if you want to get the size of each file + * in the filesystem. These sizes are not needed to use any other + * functions in libdvdread. + * + * @param dvd A dvd read handle. + * @param titlenum Which Video Title Set should be used, VIDEO_TS is 0. + * @param domain Which domain. + * @param stat Pointer to where the result is stored. + * @return If successful 0, otherwise -1. + * + * int DVDFileStat(dvd, titlenum, domain, stat); + */ +int DVDFileStat(dvd_reader_t *, int, dvd_read_domain_t, dvd_stat_t *); + +/** + * Opens a file on the DVD given the title number and domain. + * + * If the title number is 0, the video manager information is opened + * (VIDEO_TS.[IFO,BUP,VOB]). Returns a file structure which may be + * used for reads, or 0 if the file was not found. + * + * @param dvd A dvd read handle. + * @param titlenum Which Video Title Set should be used, VIDEO_TS is 0. + * @param domain Which domain. + * @return If successful a a file read handle is returned, otherwise 0. + * + * dvd_file = DVDOpenFile(dvd, titlenum, domain); */ +dvd_file_t *DVDOpenFile( dvd_reader_t *, int, dvd_read_domain_t ); + +/** + * Closes a file and frees the associated structure. + * + * @param dvd_file The file read handle to be closed. + * + * DVDCloseFile(dvd_file); + */ +void DVDCloseFile( dvd_file_t * ); + +/** + * Reads block_count number of blocks from the file at the given block offset. + * Returns number of blocks read on success, -1 on error. This call is only + * for reading VOB data, and should not be used when reading the IFO files. + * When reading from an encrypted drive, blocks are decrypted using libdvdcss + * where required. + * + * @param dvd_file A file read handle. + * @param offset Block offset from the start of the file to start reading at. + * @param block_count Number of block to read. + * @param data Pointer to a buffer to write the data into. + * @return Returns number of blocks read on success, -1 on error. + * + * blocks_read = DVDReadBlocks(dvd_file, offset, block_count, data); + */ +ssize_t DVDReadBlocks( dvd_file_t *, int, size_t, unsigned char * ); + +/** + * Seek to the given position in the file. Returns the resulting position in + * bytes from the beginning of the file. The seek position is only used for + * byte reads from the file, the block read call always reads from the given + * offset. + * + * @param dvd_file A file read handle. + * @param seek_offset Byte offset from the start of the file to seek to. + * @return The resulting position in bytes from the beginning of the file. + * + * offset_set = DVDFileSeek(dvd_file, seek_offset); + */ +int32_t DVDFileSeek( dvd_file_t *, int32_t ); + +/** + * Reads the given number of bytes from the file. This call can only be used + * on the information files, and may not be used for reading from a VOB. This + * reads from and increments the currrent seek position for the file. + * + * @param dvd_file A file read handle. + * @param data Pointer to a buffer to write the data into. + * @param bytes Number of bytes to read. + * @return Returns number of bytes read on success, -1 on error. + * + * bytes_read = DVDReadBytes(dvd_file, data, bytes); + */ +ssize_t DVDReadBytes( dvd_file_t *, void *, size_t ); + +/** + * Returns the file size in blocks. + * + * @param dvd_file A file read handle. + * @return The size of the file in blocks, -1 on error. + * + * blocks = DVDFileSize(dvd_file); + */ +ssize_t DVDFileSize( dvd_file_t * ); + +/** + * Get a unique 128 bit disc ID. + * This is the MD5 sum of VIDEO_TS.IFO and the VTS_0?_0.IFO files + * in title order (those that exist). + * If you need a 'text' representation of the id, print it as a + * hexadecimal number, using lowercase letters, discid[0] first. + * I.e. the same format as the command-line 'md5sum' program uses. + * + * @param dvd A read handle to get the disc ID from + * @param discid The buffer to put the disc ID into. The buffer must + * have room for 128 bits (16 chars). + * @return 0 on success, -1 on error. + */ +int DVDDiscID( dvd_reader_t *, unsigned char * ); + +/** + * Get the UDF VolumeIdentifier and VolumeSetIdentifier + * from the PrimaryVolumeDescriptor. + * + * @param dvd A read handle to get the disc ID from + * @param volid The buffer to put the VolumeIdentifier into. + * The VolumeIdentifier is latin-1 encoded (8bit unicode) + * null terminated and max 32 bytes (including '\0') + * @param volid_size No more than volid_size bytes will be copied to volid. + * If the VolumeIdentifier is truncated because of this + * it will still be null terminated. + * @param volsetid The buffer to put the VolumeSetIdentifier into. + * The VolumeIdentifier is 128 bytes as + * stored in the UDF PrimaryVolumeDescriptor. + * Note that this is not a null terminated string. + * @param volsetid_size At most volsetid_size bytes will be copied to volsetid. + * @return 0 on success, -1 on error. + */ +int DVDUDFVolumeInfo( dvd_reader_t *, char *, unsigned int, + unsigned char *, unsigned int ); + +int DVDFileSeekForce( dvd_file_t *, int offset, int force_size); + +/** + * Get the ISO9660 VolumeIdentifier and VolumeSetIdentifier + * + * * Only use this function as fallback if DVDUDFVolumeInfo returns 0 * + * * this will happen on a disc mastered only with a iso9660 filesystem * + * * All video DVD discs have UDF filesystem * + * + * @param dvd A read handle to get the disc ID from + * @param volid The buffer to put the VolumeIdentifier into. + * The VolumeIdentifier is coded with '0-9','A-Z','_' + * null terminated and max 33 bytes (including '\0') + * @param volid_size No more than volid_size bytes will be copied to volid. + * If the VolumeIdentifier is truncated because of this + * it will still be null terminated. + * @param volsetid The buffer to put the VolumeSetIdentifier into. + * The VolumeIdentifier is 128 bytes as + * stored in the ISO9660 PrimaryVolumeDescriptor. + * Note that this is not a null terminated string. + * @param volsetid_size At most volsetid_size bytes will be copied to volsetid. + * @return 0 on success, -1 on error. + */ +int DVDISOVolumeInfo( dvd_reader_t *, char *, unsigned int, + unsigned char *, unsigned int ); + +/** + * Sets the level of caching that is done when reading from a device + * + * @param dvd A read handle to get the disc ID from + * @param level The level of caching wanted. + * -1 - returns the current setting. + * 0 - UDF Cache turned off. + * 1 - (default level) Pointers to IFO files and some data from + * PrimaryVolumeDescriptor are cached. + * + * @return The level of caching. + */ +int DVDUDFCacheLevel( dvd_reader_t *, int ); diff --git a/dvd_ripper.py b/dvd_ripper.py index ece53fa..4a5e51f 100644 --- a/dvd_ripper.py +++ b/dvd_ripper.py @@ -1,65 +1,99 @@ -import cffi -import os -import sys -import time -from dvdnav import DVDNav,DVDError -from dvdread import DVDRead -import subprocess as SP -import json -from glob import glob -import itertools as ITT -from vob_demux import demux -from ff_d2v import make_d2v - -def loadlib(dll_path, *includes, **kwargs): - ffi = cffi.FFI() - for include in includes: - ffi.cdef(open(include).read(), kwargs) - return ffi, ffi.dlopen(dll_path) - -for dvd_path in ITT.chain.from_iterable(map(glob,sys.argv[1:])): - r = DVDRead(dvd_path) - # r.grab_ifos() - # r.grab_vobs() - # exit() - - out_folder = os.path.join( - "out", "_".join([r.disc_id, r.udf_disc_name or r.iso_disc_name]).replace(" ", "_") - ) - os.makedirs(out_folder, exist_ok=True) - d = DVDNav(dvd_path) - to_demux = [] - for k, v in d.titles.items(): - v["duration"] = v["duration"].total_seconds() - v["chapters"] = [c.total_seconds() for c in v["chapters"]] - d.titles[k] = v - with open(os.path.join(out_folder, f"{k:03}.json"), "w") as fh: - json.dump(v, fh) - for a in range(0,99): - block=0 - outfile = os.path.join(out_folder, f"t{k:03}_a{a:03}_b{block:03}.vob") - to_demux.append(outfile) - fh = open(outfile, "wb") - try: - for block in d.get_blocks(k, a): - if isinstance(block, int): - outfile = os.path.join(out_folder, f"t{k:03}_a{a:03}_b{block:03}.vob") - to_demux.append(outfile) - if fh: - fh.close() - fh = open(outfile, "wb") - else: - fh.write(block) - except DVDError as e: - if str(e)!="Invalid angle specified!": - raise - if fh.tell()==0: - fh.close() - os.unlink(fh.name) - while fh.name in to_demux: - to_demux.remove(fh.name) - for file in to_demux: - demux(file) - os.unlink(file) - for file in glob(os.path.join(out_folder,"*.m2v")): - make_d2v(file) +import itertools as ITT +import json +import os +import subprocess as SP +import sys +import time +from glob import glob + +import cffi +from datetime import timedelta +from dvdnav import DVDError, DVDNav +from dvdread import DVDRead +from ff_d2v import make_d2v, make_meta +from vob_demux import demux + +def close_file_del_if_empty(fh): + if not fh: + return False + if fh.tell() == 0: + fh.close() + os.unlink(fh.name) + return False + else: + fh.close() + return True + + +dur_thr = 60.0 + +def process_m2v_files(path): + for file in glob(os.path.join(path,"**", "*.m2v")): + make_meta(file) + make_d2v(file) + + +for dvd_path in ITT.chain.from_iterable(map(glob, sys.argv[1:])): + r = DVDRead(dvd_path) + # r.grab_ifos() + # r.grab_vobs() + # exit() + if os.path.isfile(dvd_path): + basename = os.path.splitext(os.path.basename(dvd_path))[0] + else: + basename = r.iso_disc_name or r.udf_disc_name + base_dir = os.path.join("out", "_".join([basename, r.disc_id]).replace(" ", "_")) + if os.path.isdir(base_dir): + print(f"Output foldrer {base_dir} exists, remove to re-rip DVD") + process_m2v_files(base_dir) + continue + os.makedirs(base_dir, exist_ok=True) + d = DVDNav(dvd_path) + to_demux = [] + for k, v in d.titles.items(): + out_folder=os.path.join(base_dir,f"t{k:03}") + v["duration"] = v["duration"].total_seconds() + if v["chapters"]: + v["chapters"] = [0.0]+[c.total_seconds() for c in v["chapters"]] + avg_chapter_len = v["duration"] / len(v["chapters"]) + # if avg_chapter_len<10: + # continue + d.titles[k] = v + # if not v.get('audio'): + # print(f"[{k}|0] Skipping title {k} because it has no audio tracks") + # continue + # if not v.get('vts'): + # print(f"[{k}|0] Skipping title {k} because it has no title sets") + # continue + if v["duration"] < dur_thr: + print( + f"[{k}|0] Skipping title {k} because it is shorter than {dur_thr} seconds ({v['duration']} seconds)" + ) + continue + os.makedirs(out_folder, exist_ok=True) + with open(os.path.join(out_folder, f"title.json"), "w") as fh: + json.dump(d.titles[k], fh, indent=4) + with open(os.path.join(out_folder, f"chapters.txt"), "w") as fh: + if set(v["chapters"])==set([0.0]): + continue + for n,t in enumerate(v["chapters"],1): + if abs(t-v["duration"])<1.0: + continue + print(f"CHAPTER{n:02}={timedelta(seconds=t)}",file=fh) + print(f"CHAPTER{n:02}NAME=Chapter {n}",file=fh) + for a in range(0, 99): + outfile = os.path.join(out_folder, f"{a:03}.vob") + to_demux.append(outfile) + fh = open(outfile, "wb") + try: + for block in d.get_blocks(k, a): + fh.write(block) + except DVDError as e: + if str(e) != "Invalid angle specified!": + raise + close_file_del_if_empty(fh) + to_demux = list(filter(os.path.isfile, to_demux)) + for file in to_demux: + demux(file) + os.unlink(file) + process_m2v_files(base_dir) diff --git a/dvdcss.h b/dvdcss.h index 1bdfa5f..32d0295 100644 --- a/dvdcss.h +++ b/dvdcss.h @@ -1,10 +1,10 @@ -typedef struct dvdcss_s * dvdcss_t; -typedef struct dvdcss_stream_cb dvdcss_stream_cb; -dvdcss_t dvdcss_open (const char *psz_target); -dvdcss_t dvdcss_open_stream (void *p_stream, dvdcss_stream_cb *p_stream_cb); -int dvdcss_close (dvdcss_t); -int dvdcss_seek (dvdcss_t, int i_blocks, int i_flags); -int dvdcss_read (dvdcss_t, void *p_buffer, int i_blocks, int i_flags); -int dvdcss_readv(dvdcss_t, void *p_iovec, int i_blocks, int i_flags); -const char* dvdcss_error (const dvdcss_t); +typedef struct dvdcss_s * dvdcss_t; +typedef struct dvdcss_stream_cb dvdcss_stream_cb; +dvdcss_t dvdcss_open (const char *psz_target); +dvdcss_t dvdcss_open_stream (void *p_stream, dvdcss_stream_cb *p_stream_cb); +int dvdcss_close (dvdcss_t); +int dvdcss_seek (dvdcss_t, int i_blocks, int i_flags); +int dvdcss_read (dvdcss_t, void *p_buffer, int i_blocks, int i_flags); +int dvdcss_readv(dvdcss_t, void *p_iovec, int i_blocks, int i_flags); +const char* dvdcss_error (const dvdcss_t); int dvdcss_is_scrambled (dvdcss_t); \ No newline at end of file diff --git a/dvdnav.py b/dvdnav.py index 4a78fe9..6619347 100644 --- a/dvdnav.py +++ b/dvdnav.py @@ -1,265 +1,320 @@ -import cffi -import os -import functools -from datetime import timedelta -from tqdm import tqdm -from dvdread import DVDRead - - -def loadlib(dll_path, *includes, **kwargs): - ffi = cffi.FFI() - for include in includes: - ffi.cdef(open(include).read(), kwargs) - return ffi, ffi.dlopen(dll_path) - - -class DVDError(Exception): - pass - - -class DVDNav(object): - def __init__(self, path, verbose=None, method="disc"): - if verbose is None: - os.environ.pop("DVDCSS_VERBOSE", None) - else: - os.environ["DVDCSS_VERBOSE"] = str(verbose) - os.environ["DVDCSS_METHOD"] = method - self.dvd = None - self.ffi, self.lib = loadlib( - "libdvdnav-4.dll", - "dvd_types.h", - "dvd_reader.h", - "ifo_types.h", - "nav_types.h", - "dvdnav_events.h", - "dvdnav.h", - pack=True, - ) - self.path = path - self.titles = {} - self.open(path) - - def __del__(self): - self.__check_error(self.lib.dvdnav_close(self.dvd)) - self.dvd = None - - def __repr__(self): - return " num_angles[0]: - raise DVDError("Invalid angle specified!") - if angle != curr_angle[0]: - self.__check_error(self.lib.dvdnav_angle_change(self.dvd, angle)) - if slang is not None: - self.__check_error(self.lib.dvdnav_spu_language_select(self.dvd, slang)) - event = self.lib.DVDNAV_NOP - buf = self.ffi.new("char[]", 4096) - ev = self.ffi.new("int32_t*", self.lib.DVDNAV_NOP) - size = self.ffi.new("int32_t*", 0) - pos = self.ffi.new("uint32_t*", 0) - total_size = self.ffi.new("uint32_t*", 0) - domains = { - 1: "FirstPlay", - 2: "VTSTitle", - 4: "VMGM", - 8: "VTSMenu", - } - events = { - 0: "DVDNAV_BLOCK_OK", - 1: "DVDNAV_NOP", - 2: "DVDNAV_STILL_FRAME", - 3: "DVDNAV_SPU_STREAM_CHANGE", - 4: "DVDNAV_AUDIO_STREAM_CHANGE", - 5: "DVDNAV_VTS_CHANGE", - 6: "DVDNAV_CELL_CHANGE", - 7: "DVDNAV_NAV_PACKET", - 8: "DVDNAV_STOP", - 9: "DVDNAV_HIGHLIGHT", - 10: "DVDNAV_SPU_CLUT_CHANGE", - 12: "DVDNAV_HOP_CHANNEL", - 13: "DVDNAV_WAIT", - } - progbar = tqdm( - unit_divisor=1024, - unit_scale=True, - unit="iB", - desc="Ripping DVD", - disable=False, - ) - ripped = set() - current_vts = None - current_cell = None - current_pg = None - while True: - self.__check_error(self.lib.dvdnav_get_next_block(self.dvd, buf, ev, size)) - if ( - self.lib.dvdnav_get_position(self.dvd, pos, total_size) - == self.lib.DVDNAV_STATUS_OK - ): - progbar.total = total_size[0] * 2048 - progbar.n = max(progbar.n, min(progbar.total, pos[0] * 2048)) - progbar.update(0) - progbar.set_postfix( - vts=current_vts, - cell=current_cell, - pg=current_pg, - angle=angle, - title=title, - ) - # print("Got event:",events.get(ev[0],ev[0]),size[0]) - if ev[0] in [ - self.lib.DVDNAV_SPU_CLUT_CHANGE, - self.lib.DVDNAV_HOP_CHANNEL, - self.lib.DVDNAV_NOP, - self.lib.DVDNAV_HIGHLIGHT, - ]: - continue - elif ev[0] == self.lib.DVDNAV_BLOCK_OK: - yield self.ffi.buffer(buf, size[0])[:] - elif ev[0] == self.lib.DVDNAV_STOP: - progbar.write(f"[{title}|{angle}] Stop") - break - elif ev[0] == self.lib.DVDNAV_NAV_PACKET: - pass - elif ev[0] == self.lib.DVDNAV_STILL_FRAME: - self.__check_error(self.lib.dvdnav_still_skip(self.dvd)) - elif ev[0] == self.lib.DVDNAV_WAIT: - self.__check_error(self.lib.dvdnav_wait_skip(self.dvd)) - elif ev[0] == self.lib.DVDNAV_SPU_STREAM_CHANGE: - pass - elif ev[0] == self.lib.DVDNAV_AUDIO_STREAM_CHANGE: - audio = self.ffi.cast("dvdnav_audio_stream_change_event_t*", buf) - elif ev[0] == self.lib.DVDNAV_CELL_CHANGE: - cell = self.ffi.cast("dvdnav_cell_change_event_t*", buf) - current_cell = cell.cellN - current_pg = cell.pgN - progbar.write( - f"[{title}|{angle}] Cell: {cell.cellN} ({cell.cell_start}-{cell.cell_start+cell.cell_length}), PG: {cell.pgN} ({cell.pg_start}-{cell.pg_start+cell.pg_length})" - ) - elif ev[0] == self.lib.DVDNAV_VTS_CHANGE: - vts = self.ffi.cast("dvdnav_vts_change_event_t*", buf) - new_vts = (vts.new_vtsN, vts.new_domain) - ripped.add((vts.old_vtsN, vts.old_domain)) - # progbar.write(f"[{title}|{angle}] VTS: {vts.old_vtsN} ({vts.old_domain} {old_domain}) -> {vts.new_vtsN} ({vts.new_domain} {new_domain})") - if new_vts in ripped: # looped - progbar.write(f"[{title}|{angle}] Looped!") - break - current_vts = (vts.new_vtsN, vts.new_domain) - if vts.new_domain == 8: # back to menu - progbar.write(f"[{title}|{angle}] Back to menu!") - break - yield vts.new_vtsN - else: - progbar.write( - f"[{title}|{angle}] Unhandled: {events.get(ev[0],ev[0])} {size[0]}" - ) - self.__check_error(self.lib.dvdnav_stop(self.dvd)) - - def __check_error(self, ret): - if ret == self.lib.DVDNAV_STATUS_ERR: - if self.dvd: - err = self.ffi.string(self.lib.dvdnav_err_to_string(self.dvd)) - raise DVDError(err) - raise DVDError("Unknown error") - - def __get_titles(self): - titles = self.ffi.new("int32_t*", 0) - p_times = self.ffi.new("uint64_t[]", 512) - times = self.ffi.new("uint64_t**", p_times) - duration = self.ffi.new("uint64_t*", 0) - titles = self.ffi.new("int32_t*", 0) - self.lib.dvdnav_get_number_of_titles(self.dvd, titles) - num_titles = titles[0] - for title in range(0, num_titles + 1): - if self.lib.dvdnav_get_number_of_parts(self.dvd, title, titles) == 0: - continue - num_parts = titles[0] - self.lib.dvdnav_get_number_of_angles(self.dvd, title, titles) - num_angles = titles[0] - num_chapters = self.lib.dvdnav_describe_title_chapters( - self.dvd, title, times, duration - ) - if duration[0] == 0: - continue - chapters = [] - for t in range(num_chapters): - chapters.append(timedelta(seconds=times[0][t] / 90000)) - self.titles[title] = { - "parts": num_parts, - "angles": num_angles, - "duration": timedelta(seconds=duration[0] / 90000), - "chapters": chapters, - } - - def __get_info(self): - s = self.ffi.new("char**", self.ffi.NULL) - self.lib.dvdnav_get_title_string(self.dvd, s) - self.title = str(self.ffi.string(s[0]), "utf8").strip() or None - self.lib.dvdnav_get_serial_string(self.dvd, s) - self.serial = str(self.ffi.string(s[0]), "utf8").strip() or None - self.__get_titles() - - def open(self, path): - audio_attrs = self.ffi.new("audio_attr_t*") - spu_attr = self.ffi.new("subp_attr_t*") - dvdnav = self.ffi.new("dvdnav_t**", self.ffi.cast("dvdnav_t*", 0)) - self.__check_error(self.lib.dvdnav_open(dvdnav, bytes(path, "utf8"))) - self.dvd = dvdnav[0] - self.__check_error(self.lib.dvdnav_set_readahead_flag(self.dvd, 1)) - self.__get_info() - for title in self.titles: - self.__check_error(self.lib.dvdnav_title_play(self.dvd, title)) - self.titles[title]["audio"] = {} - self.titles[title]["subtitles"] = {} - for n in range(255): - stream_id = self.lib.dvdnav_get_audio_logical_stream(self.dvd, n) - if stream_id == -1: - continue - self.__check_error( - self.lib.dvdnav_get_audio_attr(self.dvd, stream_id, audio_attrs) - ) - alang = None - if audio_attrs.lang_type: - alang = str(audio_attrs.lang_code.to_bytes(2, "big"), "utf8") - channels = audio_attrs.channels + 1 - codec = {0: "ac3", 2: "mpeg1", 3: "mpeg-2ext", 4: "lpcm", 6: "dts"}[ - audio_attrs.audio_format - ] - audio_type = { - 0: None, - 1: "normal", - 2: "descriptive", - 3: "director's commentary", - 4: "alternate director's commentary", - }[audio_attrs.code_extension] - self.titles[title]["audio"][n] = { - "stream_id": stream_id, - "lang": alang, - "channels": channels, - "codec": codec, - "type": audio_type, - } - for n in range(255): - stream_id = self.lib.dvdnav_get_spu_logical_stream(self.dvd, n) - if stream_id == -1: - continue - self.__check_error( - self.lib.dvdnav_get_spu_attr(self.dvd, stream_id, spu_attr) - ) - slang = None - if spu_attr.type == 1: - slang = str(spu_attr.lang_code.to_bytes(2, "big"), "utf8") - self.titles[title]["subtitles"][n] = { - "stream_id": stream_id, - "lang": slang, - } - self.__check_error(self.lib.dvdnav_stop(self.dvd)) +import functools +import os +from datetime import timedelta + +import cffi +from tqdm import tqdm + +from dvdread import DVDRead + + +def loadlib(dll_path, *includes, **kwargs): + ffi = cffi.FFI() + for include in includes: + ffi.cdef(open(include).read(), kwargs) + return ffi, ffi.dlopen(dll_path) + + +domains = { + 0: "None", + 1: "FirstPlay", + 2: "VTSTitle", + 4: "VMGM", + 8: "VTSMenu", +} +events = { + 0: "DVDNAV_BLOCK_OK", + 1: "DVDNAV_NOP", + 2: "DVDNAV_STILL_FRAME", + 3: "DVDNAV_SPU_STREAM_CHANGE", + 4: "DVDNAV_AUDIO_STREAM_CHANGE", + 5: "DVDNAV_VTS_CHANGE", + 6: "DVDNAV_CELL_CHANGE", + 7: "DVDNAV_NAV_PACKET", + 8: "DVDNAV_STOP", + 9: "DVDNAV_HIGHLIGHT", + 10: "DVDNAV_SPU_CLUT_CHANGE", + 12: "DVDNAV_HOP_CHANNEL", + 13: "DVDNAV_WAIT", +} + +class DVDError(Exception): + pass + + +class DVDNav(object): + def __init__(self, path, verbose=None, method="disc"): + if verbose is None: + os.environ.pop("DVDCSS_VERBOSE", None) + else: + os.environ["DVDCSS_VERBOSE"] = str(verbose) + os.environ["DVDCSS_METHOD"] = method + self.dvd = None + self.ffi, self.lib = loadlib( + "libdvdnav-4.dll", + "dvd_types.h", + "dvd_reader.h", + "ifo_types.h", + "nav_types.h", + "dvdnav_events.h", + "dvdnav.h", + pack=True, + ) + self.path = path + self.titles = {} + self.open(path) + + def __del__(self): + self.__check_error(self.lib.dvdnav_close(self.dvd)) + self.dvd = None + + def __repr__(self): + return " num_angles[0]: + raise DVDError("Invalid angle specified!") + if angle != curr_angle[0]: + self.__check_error(self.lib.dvdnav_angle_change(self.dvd, angle)) + if slang is not None: + self.__check_error(self.lib.dvdnav_spu_language_select(self.dvd, slang)) + event = self.lib.DVDNAV_NOP + buf = self.ffi.new("char[]", 4096) + ev = self.ffi.new("int32_t*", self.lib.DVDNAV_NOP) + size = self.ffi.new("int32_t*", 0) + pos = self.ffi.new("uint32_t*", 0) + total_size = self.ffi.new("uint32_t*", 0) + progbar = tqdm( + unit_divisor=1024, + unit_scale=True, + unit="iB", + desc="Ripping DVD", + disable=False, + ) + ripped = set() + cells = set() + current_vts = (None,None) + current_cell = None + current_pg = None + while True: + self.__check_error(self.lib.dvdnav_get_next_block(self.dvd, buf, ev, size)) + if ( + self.lib.dvdnav_get_position(self.dvd, pos, total_size) + == self.lib.DVDNAV_STATUS_OK + ): + progbar.total = total_size[0] * 2048 + progbar.n = max(progbar.n, min(progbar.total, pos[0] * 2048)) + progbar.update(0) + progbar.set_postfix( + vts=current_vts, + cell=current_cell, + pg=current_pg, + angle=angle, + title=title, + ) + # print("Got event:",events.get(ev[0],ev[0]),size[0]) + if ev[0] in [ + self.lib.DVDNAV_SPU_CLUT_CHANGE, + self.lib.DVDNAV_HOP_CHANNEL, + self.lib.DVDNAV_NOP, + self.lib.DVDNAV_HIGHLIGHT, + ]: + continue + elif ev[0] == self.lib.DVDNAV_BLOCK_OK: + yield self.ffi.buffer(buf, size[0])[:] + elif ev[0] == self.lib.DVDNAV_STOP: + progbar.write(f"[{title}|{angle}] Stop") + break + elif ev[0] == self.lib.DVDNAV_NAV_PACKET: + pass + elif ev[0] == self.lib.DVDNAV_STILL_FRAME: + self.__check_error(self.lib.dvdnav_still_skip(self.dvd)) + elif ev[0] == self.lib.DVDNAV_WAIT: + self.__check_error(self.lib.dvdnav_wait_skip(self.dvd)) + elif ev[0] == self.lib.DVDNAV_SPU_STREAM_CHANGE: + pass + elif ev[0] == self.lib.DVDNAV_AUDIO_STREAM_CHANGE: + audio = self.ffi.cast("dvdnav_audio_stream_change_event_t*", buf) + elif ev[0] == self.lib.DVDNAV_CELL_CHANGE: + cell = self.ffi.cast("dvdnav_cell_change_event_t*", buf) + current_cell = cell.cellN + current_pg = cell.pgN + progbar.write( + f"[{title}|{angle}] Cell: {cell.cellN} ({hex(cell.cell_start)}-{hex(cell.cell_start+cell.cell_length)}), PG: {cell.pgN} ({hex(cell.pg_start)}-{hex(cell.pg_start+cell.pg_length)})" + ) + fp=(current_vts[0],current_vts[1],cell.cellN,cell.pgN,cell.cell_length,cell.pg_length,cell.pgc_length,cell.cell_start,cell.pg_start) + if fp in cells: + progbar.write(f"[{title}|{angle}] Cells Looped!") + break + cells.add(fp) + elif ev[0] == self.lib.DVDNAV_VTS_CHANGE: + vts = self.ffi.cast("dvdnav_vts_change_event_t*", buf) + old_domain = domains[vts.old_domain] + new_domain = domains[vts.new_domain] + new_vts = (vts.new_vtsN, vts.new_domain) + old_vts = (vts.old_vtsN, vts.old_domain) + ripped.add((vts.old_vtsN, vts.old_domain)) + cells.clear() + progbar.write(f"[{title}|{angle}] VTS: {vts.old_vtsN} ({vts.old_domain} {old_domain}) -> {vts.new_vtsN} ({vts.new_domain} {new_domain})") + if (new_vts in ripped) or new_vts==old_vts: # looped + progbar.write(f"[{title}|{angle}] VTS Looped!") + break + current_vts = (vts.new_vtsN, vts.new_domain) + if vts.new_domain == 8: # back to menu + progbar.write(f"[{title}|{angle}] VTS Back to menu!") + break + # yield vts.new_vtsN + else: + progbar.write( + f"[{title}|{angle}] Unhandled: {events.get(ev[0],ev[0])} {size[0]}" + ) + self.__check_error(self.lib.dvdnav_stop(self.dvd)) + progbar.close() + + def __check_error(self, ret): + if ret == self.lib.DVDNAV_STATUS_ERR: + if self.dvd: + err = self.ffi.string(self.lib.dvdnav_err_to_string(self.dvd)) + raise DVDError(err) + raise DVDError("Unknown error") + + def __get_vts(self,title): + buf = self.ffi.new("char[]", 4096) + ev = self.ffi.new("int32_t*", self.lib.DVDNAV_NOP) + size = self.ffi.new("int32_t*", 0) + pos = self.ffi.new("uint32_t*", 0) + total_size = self.ffi.new("uint32_t*", 0) + self.__check_error(self.lib.dvdnav_set_PGC_positioning_flag(self.dvd, 1)) + self.__check_error(self.lib.dvdnav_title_play(self.dvd, title)) + seq=[] + while True: + self.__check_error(self.lib.dvdnav_get_next_block(self.dvd, buf, ev, size)) + if ev[0] == self.lib.DVDNAV_BLOCK_OK: + self.__check_error(self.lib.dvdnav_get_position(self.dvd, pos, total_size)) + # print(title,pos[0],total_size[0]) + if self.lib.dvdnav_next_pg_search(self.dvd)==0: + break + elif ev[0] == self.lib.DVDNAV_STOP: + break + elif ev[0] == self.lib.DVDNAV_STILL_FRAME: + self.__check_error(self.lib.dvdnav_still_skip(self.dvd)) + elif ev[0] == self.lib.DVDNAV_WAIT: + self.__check_error(self.lib.dvdnav_wait_skip(self.dvd)) + elif ev[0] == self.lib.DVDNAV_VTS_CHANGE: + vts = self.ffi.cast("dvdnav_vts_change_event_t*", buf) + old_domain = domains[vts.old_domain] + new_domain = domains[vts.new_domain] + seq.append( + (vts.new_vtsN, new_domain) + ) + if vts.new_domain==8: + break + continue + # print(title,ev[0],size[0]) + self.__check_error(self.lib.dvdnav_stop(self.dvd)) + # print(title,seq) + return seq + # self.__check_error(self.lib.dvdnav_next_pg_search(self.dvd)) + + def __get_titles(self): + titles = self.ffi.new("int32_t*", 0) + p_times = self.ffi.new("uint64_t[]", 512) + times = self.ffi.new("uint64_t**", p_times) + duration = self.ffi.new("uint64_t*", 0) + titles = self.ffi.new("int32_t*", 0) + self.lib.dvdnav_get_number_of_titles(self.dvd, titles) + num_titles = titles[0] + for title in range(0, num_titles + 1): + if self.lib.dvdnav_get_number_of_parts(self.dvd, title, titles) == 0: + continue + num_parts = titles[0] + self.lib.dvdnav_get_number_of_angles(self.dvd, title, titles) + num_angles = titles[0] + num_chapters = self.lib.dvdnav_describe_title_chapters( + self.dvd, title, times, duration + ) + if duration[0] == 0: + continue + chapters = [] + if num_chapters==0 and times[0]==self.ffi.NULL: + chapters=None + for t in range(num_chapters): + chapters.append(timedelta(seconds=times[0][t] / 90000)) + self.titles[title] = { + "parts": num_parts, + "angles": num_angles, + "duration": timedelta(seconds=duration[0] / 90000), + "chapters": chapters, + } + + def __get_info(self): + s = self.ffi.new("char**", self.ffi.NULL) + self.lib.dvdnav_get_title_string(self.dvd, s) + self.title = str(self.ffi.string(s[0]), "utf8").strip() or None + self.lib.dvdnav_get_serial_string(self.dvd, s) + self.serial = str(self.ffi.string(s[0]), "utf8").strip() or None + self.__get_titles() + + def open(self, path): + audio_attrs = self.ffi.new("audio_attr_t*") + spu_attr = self.ffi.new("subp_attr_t*") + dvdnav = self.ffi.new("dvdnav_t**", self.ffi.cast("dvdnav_t*", 0)) + self.__check_error(self.lib.dvdnav_open(dvdnav, bytes(path, "utf8"))) + self.dvd = dvdnav[0] + self.__check_error(self.lib.dvdnav_set_readahead_flag(self.dvd, 1)) + self.__get_info() + for title in self.titles: + self.__check_error(self.lib.dvdnav_title_play(self.dvd, title)) + self.titles[title]["audio"] = {} + self.titles[title]["subtitles"] = {} + # self.titles[title]["vts"] = self.__get_vts(title) + for n in range(255): + stream_id = self.lib.dvdnav_get_audio_logical_stream(self.dvd, n) + if stream_id == -1: + continue + self.__check_error( + self.lib.dvdnav_get_audio_attr(self.dvd, stream_id, audio_attrs) + ) + alang = None + if audio_attrs.lang_type: + alang = str(audio_attrs.lang_code.to_bytes(2, "big"), "utf8") + channels = audio_attrs.channels + 1 + codec = {0: "ac3", 2: "mpeg1", 3: "mpeg-2ext", 4: "lpcm", 6: "dts"}[ + audio_attrs.audio_format + ] + audio_type = { + 0: None, + 1: "normal", + 2: "descriptive", + 3: "director's commentary", + 4: "alternate director's commentary", + }[audio_attrs.code_extension] + self.titles[title]["audio"][stream_id] = { + "lang": alang, + "channels": channels, + "codec": codec, + "type": audio_type, + } + for n in range(255): + stream_id = self.lib.dvdnav_get_spu_logical_stream(self.dvd, n) + if stream_id == -1: + continue + self.__check_error( + self.lib.dvdnav_get_spu_attr(self.dvd, stream_id, spu_attr) + ) + slang = None + if spu_attr.type == 1: + slang = str(spu_attr.lang_code.to_bytes(2, "big"), "utf8") + self.titles[title]["subtitles"][stream_id] = { + "lang": slang, + } + self.__check_error(self.lib.dvdnav_stop(self.dvd)) + # exit("DEBUG!") diff --git a/dvdread.py b/dvdread.py index f76348d..b37e310 100644 --- a/dvdread.py +++ b/dvdread.py @@ -1,165 +1,167 @@ -import cffi -import os -import functools -import binascii -from datetime import timedelta - - -def loadlib(dll_path, *includes, **kwargs): - ffi = cffi.FFI() - for include in includes: - ffi.cdef(open(include).read(), **kwargs) - return ffi, ffi.dlopen(dll_path) - - -class DVDRead(object): - def __init__(self, path, verbose="0", method="disc"): - if verbose is None: - os.environ.pop("DVDCSS_VERBOSE", None) - else: - os.environ["DVDCSS_VERBOSE"] = str(verbose) - os.environ["DVDCSS_METHOD"] = method - self.dvd = None - self.ffi, self.lib = loadlib( - "libdvdread-8.dll", - "dvd_types.h", - "dvd_reader.h", - "ifo_types.h", - "ifo_read.h", - "ifo_print.h", - "nav_types.h", - "nav_read.h", - "nav_print.h", - packed=True, - ) - self.path = path - self.titles = {} - self.open(path) - self.lb_len=self.lib.DVD_VIDEO_LB_LEN - - def grab_ifo(self,title,bup=False): - from tqdm import tqdm - buf = self.ffi.new("unsigned char[]", 512) - if bup: - fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_BACKUP_FILE) - else: - fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_FILE) - total_size = self.lib.DVDFileSize(fh)*self.lb_len - remaining = total_size - num_read = True - pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False) - while num_read: - num_read=self.lib.DVDReadBytes( fh, buf, 512) - num_read=min(num_read,remaining) - remaining-=num_read - pbar.update(num_read) - yield self.ffi.buffer(buf,num_read)[:] - self.lib.DVDCloseFile(fh) - - def grab_ifos(self): - vmg_ifo = self.lib.ifoOpen(self.dvd, 0) - if vmg_ifo == self.ffi.NULL: - return - title_sets = vmg_ifo.vts_atrt.nr_of_vtss - for t in range(1,title_sets + 1): - vts = self.lib.ifoOpen(self.dvd, t) - if vts == self.ffi.NULL: - continue - self.lib.ifoClose(vts) - outfile=os.path.join("RIP",f"VTS_{t:02}_0.ifo") - with open(outfile, "wb") as out_ifo: - for block in self.grab_ifo(t,bup=False): - out_ifo.write(block) - outfile=os.path.join("RIP",f"VTS_{t:02}_0.bup") - with open(outfile, "wb") as out_ifo: - for block in self.grab_ifo(t,bup=True): - out_ifo.write(block) - self.lib.ifoClose(vmg_ifo) - - def grab_vob(self,title): - from tqdm import tqdm - buf = self.ffi.new("unsigned char[]", 512 * self.lb_len) - fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_TITLE_VOBS) - total_size = self.lib.DVDFileSize(fh)*self.lb_len - remaining = total_size - num_read = True - pos=0 - pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False) - while remaining: - num_read=self.lib.DVDReadBlocks( fh,pos, 512, buf) - if num_read<0: - raise RuntimeError("Error reading!") - num_read_bytes=num_read*self.lb_len - num_read_bytes=min(num_read_bytes,remaining) - remaining-=num_read_bytes - pbar.update(num_read_bytes) - yield self.ffi.buffer(buf,num_read_bytes)[:] - pos+=num_read - pbar.close() - self.lib.DVDCloseFile(fh) - - def grab_vobs(self): - vmg_ifo = self.lib.ifoOpen(self.dvd, 0) - if vmg_ifo == self.ffi.NULL: - return - title_sets = vmg_ifo.vts_atrt.nr_of_vtss - for t in range(1,title_sets + 1): - vts = self.lib.ifoOpen(self.dvd, t) - if vts == self.ffi.NULL: - continue - self.lib.ifoClose(vts) - outfile=os.path.join("RIP",f"VTS_{t:02}_0.vob") - with open(outfile, "wb") as out_ifo: - for block in self.grab_vob(t): - out_ifo.write(block) - self.lib.ifoClose(vmg_ifo) - - - def test(self): - from tqdm import tqdm - fn = 0 - chunk_size = 2048 - buf = self.ffi.new("unsigned char[]", chunk_size * 2048) - for fn in range(title_sets + 1): - pos = 0 - fh = self.lib.DVDOpenFile(self.dvd, fn, self.lib.DVD_READ_TITLE_VOBS) - if fh: - total_size = self.lib.DVDFileSize(fh) - if total_size == -1: - self.lib.DVDCloseFile(fh) - break - pbar = tqdm(total=total_size * 2048, unit="iB", unit_scale=True, unit_divisor=1024,leave=False) - last=False - with open(f"out_{fn}.vob", "wb") as out_vob: - while True: - if (pos+chunk_size)>total_size: - chunk_size=total_size-pos - count = self.lib.DVDReadBlocks(fh, pos, chunk_size, buf) - if count == -1: - break - pbar.update( - out_vob.write(self.ffi.buffer(buf, count * 2048)[:]) - ) - pos += count - if pos>=total_size: - break - self.lib.DVDCloseFile(fh) - fn += 1 - if fn>200: - break - - def __del__(self): - if self.dvd: - self.lib.DVDClose(self.dvd) - - def open(self, path): - # self.dvd_css=self.css_lib.dvdcss_open() - self.dvd = self.lib.DVDOpen(bytes(path, "utf8")) - vol_id = self.ffi.new("unsigned char[]", 32) - self.lib.DVDDiscID(self.dvd, vol_id) - self.disc_id = str(binascii.hexlify(self.ffi.buffer(vol_id, 16)[:]), "utf8") - self.lib.DVDUDFVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0) - self.udf_disc_name = str(self.ffi.string(vol_id), "utf8") - self.lib.DVDISOVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0) - self.iso_disc_name = str(self.ffi.string(vol_id), "utf8") - self.ffi.release(vol_id) +import binascii +import functools +import os +from datetime import timedelta + +import cffi + + +def loadlib(dll_path, *includes, **kwargs): + ffi = cffi.FFI() + for include in includes: + ffi.cdef(open(include).read(), **kwargs) + return ffi, ffi.dlopen(dll_path) + + +class DVDRead(object): + def __init__(self, path, verbose="0", method="disc"): + if verbose is None: + os.environ.pop("DVDCSS_VERBOSE", None) + else: + os.environ["DVDCSS_VERBOSE"] = str(verbose) + os.environ["DVDCSS_METHOD"] = method + self.dvd = None + self.ffi, self.lib = loadlib( + "libdvdread-8.dll", + "dvd_types.h", + "dvd_reader.h", + "ifo_types.h", + "ifo_read.h", + "ifo_print.h", + "nav_types.h", + "nav_read.h", + "nav_print.h", + packed=True, + ) + self.path = path + self.titles = {} + self.open(path) + self.lb_len=self.lib.DVD_VIDEO_LB_LEN + + def grab_ifo(self,title,bup=False): + from tqdm import tqdm + buf = self.ffi.new("unsigned char[]", 512) + if bup: + fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_BACKUP_FILE) + else: + fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_FILE) + total_size = self.lib.DVDFileSize(fh)*self.lb_len + remaining = total_size + num_read = True + pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False) + while num_read: + num_read=self.lib.DVDReadBytes( fh, buf, 512) + num_read=min(num_read,remaining) + remaining-=num_read + pbar.update(num_read) + yield self.ffi.buffer(buf,num_read)[:] + self.lib.DVDCloseFile(fh) + pbar.close() + + def grab_ifos(self): + vmg_ifo = self.lib.ifoOpen(self.dvd, 0) + if vmg_ifo == self.ffi.NULL: + return + title_sets = vmg_ifo.vts_atrt.nr_of_vtss + for t in range(1,title_sets + 1): + vts = self.lib.ifoOpen(self.dvd, t) + if vts == self.ffi.NULL: + continue + self.lib.ifoClose(vts) + outfile=os.path.join("RIP",f"VTS_{t:02}_0.ifo") + with open(outfile, "wb") as out_ifo: + for block in self.grab_ifo(t,bup=False): + out_ifo.write(block) + outfile=os.path.join("RIP",f"VTS_{t:02}_0.bup") + with open(outfile, "wb") as out_ifo: + for block in self.grab_ifo(t,bup=True): + out_ifo.write(block) + self.lib.ifoClose(vmg_ifo) + + def grab_vob(self,title): + from tqdm import tqdm + buf = self.ffi.new("unsigned char[]", 512 * self.lb_len) + fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_TITLE_VOBS) + total_size = self.lib.DVDFileSize(fh)*self.lb_len + remaining = total_size + num_read = True + pos=0 + pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False) + while remaining: + num_read=self.lib.DVDReadBlocks( fh,pos, 512, buf) + if num_read<0: + raise RuntimeError("Error reading!") + num_read_bytes=num_read*self.lb_len + num_read_bytes=min(num_read_bytes,remaining) + remaining-=num_read_bytes + pbar.update(num_read_bytes) + yield self.ffi.buffer(buf,num_read_bytes)[:] + pos+=num_read + pbar.close() + self.lib.DVDCloseFile(fh) + + def grab_vobs(self): + vmg_ifo = self.lib.ifoOpen(self.dvd, 0) + if vmg_ifo == self.ffi.NULL: + return + title_sets = vmg_ifo.vts_atrt.nr_of_vtss + for t in range(1,title_sets + 1): + vts = self.lib.ifoOpen(self.dvd, t) + if vts == self.ffi.NULL: + continue + self.lib.ifoClose(vts) + outfile=os.path.join("RIP",f"VTS_{t:02}_0.vob") + with open(outfile, "wb") as out_ifo: + for block in self.grab_vob(t): + out_ifo.write(block) + self.lib.ifoClose(vmg_ifo) + + + def test(self): + from tqdm import tqdm + fn = 0 + chunk_size = 2048 + buf = self.ffi.new("unsigned char[]", chunk_size * 2048) + for fn in range(title_sets + 1): + pos = 0 + fh = self.lib.DVDOpenFile(self.dvd, fn, self.lib.DVD_READ_TITLE_VOBS) + if fh: + total_size = self.lib.DVDFileSize(fh) + if total_size == -1: + self.lib.DVDCloseFile(fh) + break + pbar = tqdm(total=total_size * 2048, unit="iB", unit_scale=True, unit_divisor=1024,leave=False) + last=False + with open(f"out_{fn}.vob", "wb") as out_vob: + while True: + if (pos+chunk_size)>total_size: + chunk_size=total_size-pos + count = self.lib.DVDReadBlocks(fh, pos, chunk_size, buf) + if count == -1: + break + pbar.update( + out_vob.write(self.ffi.buffer(buf, count * 2048)[:]) + ) + pos += count + if pos>=total_size: + break + self.lib.DVDCloseFile(fh) + fn += 1 + if fn>200: + break + + def __del__(self): + if self.dvd: + self.lib.DVDClose(self.dvd) + + def open(self, path): + # self.dvd_css=self.css_lib.dvdcss_open() + self.dvd = self.lib.DVDOpen(bytes(path, "utf8")) + vol_id = self.ffi.new("unsigned char[]", 32) + self.lib.DVDDiscID(self.dvd, vol_id) + self.disc_id = str(binascii.hexlify(self.ffi.buffer(vol_id, 16)[:]), "utf8") + self.lib.DVDUDFVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0) + self.udf_disc_name = str(self.ffi.string(vol_id), "utf8") + self.lib.DVDISOVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0) + self.iso_disc_name = str(self.ffi.string(vol_id), "utf8") + self.ffi.release(vol_id) diff --git a/ff_d2v.py b/ff_d2v.py index 55e9c7e..20770e6 100644 --- a/ff_d2v.py +++ b/ff_d2v.py @@ -1,205 +1,430 @@ -import sys -import json -import os -import subprocess as SP -import itertools as ITT -from tqdm import tqdm - - -colorspace = { - "gbr": 0, - "bt709": 1, - "unknown": 2, - "fcc": 4, - "bt470bg": 5, - "smpte170m": 6, - "smpte240m": 7, - "ycgco": 8, - "bt2020nc": 9, - "bt2020c": 10, - "smpte2085": 11, - "chroma-derived-nc": 12, - "chroma-derived-c": 13, - "ictcp": 14, -} - -pict_types = {"I": 0b01, "P": 0b10, "B": 0b11} - - -def make_info(frames): - has_interlaced = any(frame["interlaced_frame"] for frame in frames) - new_gop = "timecode" in frames[0].get("tags", {}) - info = 0x000 - info |= 1 << 11 # always 1 - info |= 0 << 10 # 0=Closed GOP, 1=Open GOP - info |= (not has_interlaced) << 9 # Progressive - info |= new_gop << 8 - return info - - -def make_flags(frames): - flags = [] - for frame in frames: - needs_prev = False - progressive = not int(frame["interlaced_frame"]) - pt = pict_types[frame["pict_type"]] - reserved = 0b00 - tff = int(frame["top_field_first"]) - rff = int(frame["repeat_pict"]) - flag = 0b0 - flag |= (not needs_prev) << 7 - flag |= progressive << 6 - flag |= pt << 4 - flag |= reserved << 2 - flag |= tff << 1 - flag |= rff - flags.append(f"{flag:02x}") - return flags - - -def make_line(frames, stream): - info = f"{make_info(frames):03x}" - matrix = colorspace[stream["color_space"]] - file = 0 - position = frames[0]["pkt_pos"] - skip = 0 - vob = 0 - cell = 0 - flags = make_flags(frames) - return " ".join(map(str, [info, matrix, file, position, skip, vob, cell, *flags])) - - -def get_frames(path): - proc = SP.Popen( - [ - "ffprobe", - "-probesize", - str(0x7FFFFFFF), - "-analyzeduration", - str(0x7FFFFFFF), - "-v", - "fatal", - "-i", - path, - "-select_streams", - "v:0", - "-show_frames", - "-print_format", - "compact", - ], - stdout=SP.PIPE, - stdin=SP.DEVNULL, - bufsize=0, - ) - data = None - for line in proc.stdout: - line = str(line, "utf8").strip().split("|") - line = {line[0]: dict(v.split("=") for v in line[1:])} - yield line - ret = proc.wait() - if ret != 0: - exit(ret) - return data - - -def get_streams(path): - proc = SP.Popen( - [ - "ffprobe", - "-probesize", - str(0x7FFFFFFF), - "-analyzeduration", - str(0x7FFFFFFF), - "-v", - "fatal", - "-i", - path, - "-select_streams", - "v:0", - "-show_streams", - "-show_format", - "-print_format", - "json", - ], - stdout=SP.PIPE, - stdin=SP.DEVNULL, - bufsize=0, - ) - data = json.load(proc.stdout) - ret = proc.wait() - if ret != 0: - exit(ret) - return data["streams"], data["format"] - - -def make_header(file): - return ["DGIndexProjectFile16", "1", os.path.abspath(file)] - - -def make_settings(stream): - pict_size = "x".join(map(str, [stream["width"], stream["height"]])) - frame_rate = list(map(int, stream["r_frame_rate"].split("/"))) - frame_rate = (frame_rate[0] * 1000) // frame_rate[1] - frame_rate = f"{frame_rate} ({stream['r_frame_rate']})" - header = [ - ("Stream_Type", 0), # Elementary Stream - ("MPEG_Type", 2), # MPEG-2 - ("iDCT_Algorithm", 5), # 64-bit IEEE-1180 Reference - ("YUVRGB_Scale", int(stream["color_range"] != "tv")), - ("Luminance_Filter", "0,0"), - ("Clipping", "0,0,0,0"), - ("Aspect_Ratio", stream["display_aspect_ratio"]), - ("Picture_Size", pict_size), - ("Field_Operation", 0), # Honor Pulldown Flags - ("Frame_Rate", frame_rate), - ("Location", "0,0,0,0"), - ] - for k, v in header: - yield f"{k}={v}" - - -def gen_d2v(path): - yield from make_header(path) - yield "" - streams, fmt = get_streams(path) - stream = [s for s in streams if s["codec_type"] == "video"][0] - stream["index"] = str(stream["index"]) - yield from make_settings(stream) - yield "" - line_buffer = [] - frames = get_frames(path) - prog_bar = tqdm( - frames, - total=int(fmt["size"]), - unit_divisor=1024, - unit_scale=True, - unit="iB", - desc="Writing d2v", - ) - for line in prog_bar: - if "frame" not in line: - continue - frame = line["frame"] - prog_bar.n = min(max(prog_bar.n, int(frame["pkt_pos"])), int(fmt["size"])) - prog_bar.update(0) - if frame["stream_index"] != stream["index"]: - continue - if frame["pict_type"] == "I" and line_buffer: - yield make_line(line_buffer, stream) - line_buffer.clear() - line_buffer.append(frame) - prog_bar.close() - yield None - - -def make_d2v(path): - outfile = os.path.splitext(os.path.basename(path))[0] - outfile = os.path.extsep.join([outfile, "d2v"]) - a, b = ITT.tee(gen_d2v(path)) - next(b) - with open(outfile, "w") as fh: - for line, next_line in zip(a, b): - fh.write(line) - if next_line is None: # last line, append end marker - fh.write(" ff") - fh.write("\n") +import itertools as ITT +import json +import os +import subprocess as SP +import sys +from pprint import pprint +from fractions import Fraction +from glob import glob +from collections import Counter +from tqdm import tqdm +from time import perf_counter + + +def pulldown(fields_per_second, frames_per_second): + f = Fraction(fields_per_second, frames_per_second) + + +colorspace = { + "gbr": 0, + "bt709": 1, + "unknown": 2, + "fcc": 4, + "bt470bg": 5, + "smpte170m": 6, + "smpte240m": 7, + "ycgco": 8, + "bt2020nc": 9, + "bt2020c": 10, + "smpte2085": 11, + "chroma-derived-nc": 12, + "chroma-derived-c": 13, + "ictcp": 14, +} + +pict_types = {"I": 0b01, "P": 0b10, "B": 0b11} + + +def make_info(frames): + has_interlaced = any(frame["interlaced_frame"] for frame in frames) + new_gop = "timecode" in frames[0].get("tags", {}) + info = 0x000 + info |= 1 << 11 # always 1 + info |= 0 << 10 # 0=Closed GOP, 1=Open GOP + info |= (not has_interlaced) << 9 # Progressive + info |= new_gop << 8 + return info + + +def make_flags(frames): + flags = [] + for frame in frames: + needs_prev = False + progressive = not int(frame["interlaced_frame"]) + pt = pict_types[frame["pict_type"]] + reserved = 0b00 + tff = int(frame["top_field_first"]) + rff = int(frame["repeat_pict"]) + flag = 0b0 + flag |= (not needs_prev) << 7 + flag |= progressive << 6 + flag |= pt << 4 + flag |= reserved << 2 + flag |= tff << 1 + flag |= rff + flags.append(f"{flag:02x}") + return flags + + +def make_line(frames, stream): + info = f"{make_info(frames):03x}" + matrix = colorspace[stream.get("color_space", "unknown")] + file = 0 + position = frames[0]["pkt_pos"] + skip = 0 + vob = 0 + cell = 0 + flags = make_flags(frames) + return " ".join(map(str, [info, matrix, file, position, skip, vob, cell, *flags])) + + +def __make_dict(line): + ret = {} + line = line.strip().split("|") + line_type = line[0] + for value in line[1:]: + entry = ret + if "=" not in value: + continue + key_path, value = value.split("=") + key_path = key_path.split(".") + for key in key_path[:-1]: + if ":" in key: + key = key.split(":")[1] + entry = entry.setdefault(key, {}) + entry[key_path[-1]] = value + return {line_type: ret} + + +def judge(info, num_frames): + threshold = 1 # BFF/TFF threshold value + min_num_frames = 250 # minimal number of frames + idet = info["frame"]["lavfi"]["idet"] + idet_v = {} + for t in "repeated", "single", "multiple": + idet_v[t] = {} + for k, v in idet[t].items(): + try: + idet_v[t][k] = int(v) + except ValueError: + try: + idet_v[t][k] = float(v) + except ValueError: + pass + idet = { + "repeat": {k: v for k, v in idet_v["repeated"].items()}, + "single": {k: v for k, v in idet_v["single"].items()}, + "multiple": {k: v for k, v in idet_v["multiple"].items()}, + } + repeat_err = abs( + (idet["repeat"]["neither"] / num_frames) - 0.8 + ) # 2:3 pulldown,4 frames @ ~24 FPS to ~30 FPS = 20% repeated fields + print(f"Derivation from 2:3 Pulldown: {repeat_err:.2%}") + tff = idet["multiple"]["tff"] + bff = idet["multiple"]["bff"] + progressive = idet["multiple"]["progressive"] + interlaced = tff + bff + determined = interlaced + progressive + print(f"Determined: {determined}") + if interlaced: + print(f"Interlaced: {interlaced} (TFF: {tff/interlaced:.2%}, BFF: {bff/interlaced:.2%}) = {interlaced/determined:.2%}") + else: + print(f"Interlaced: {interlaced} = {interlaced/determined:.2%}") + print(f"Progressive: {progressive} = {progressive/determined:.2%}") + if determined == 0: + return idet + idet["num_frames"] = num_frames + idet["interlaced"] = interlaced + idet["progressive"] = progressive + if determined < 50 or determined < min_num_frames: + print("/!\\ Not enough information to determine interlacing type reliably, results may be inacurate /!\\") + if interlaced > progressive: + if tff > bff: + if repeat_err < 1.0: + idet["vid_type"] = "Telecined TFF" + else: + idet["vid_type"] = "Interlaced TFF" + elif bff > tff: + if repeat_err < 1.0: + idet["vid_type"] = "Telecined BFF" + else: + idet["vid_type"] = "Interlaced BFF" + else: + idet["vid_type"] = "Interlaced?" + else: + idet["vid_type"] = "Progressive" + print(f"Result: {idet['vid_type']}") + return idet + + +def get_meta_interlacing(path): + path = path.replace("\\", "/") + filtergraph = [ + f"movie=\\'{path}\\'", + "cropdetect=limit=0.5:round=2", + "idet", + ] + proc = SP.Popen( + [ + "ffprobe", + "-loglevel", + "fatal", + "-probesize", + str(0x7FFFFFFF), + "-analyzeduration", + str(0x7FFFFFFF), + "-f", + "lavfi", + "-i", + ",".join(filtergraph), + "-select_streams", + "v", + "-show_frames", + "-show_streams", + "-print_format", + "compact", + ], + stdout=SP.PIPE, + stdin=SP.DEVNULL, + bufsize=0, + encoding="utf8", + ) + total_size = int(get_streams(path)[1]["size"]) + data = {} + pbar = tqdm( + total=total_size, + desc="Analyzing video", + unit_divisor=1024, + unit_scale=True, + unit="iB", + leave=False, + ) + frame_num = 0 + from pprint import pformat, pprint + + pattern = [] + for line in proc.stdout: + line = __make_dict(line) + data.update(line) + if "frame" in line: + frame_num += 1 + pbar.n = max(pbar.n, min(total_size, int(line["frame"]["pkt_pos"]))) + dt = pbar._time() - pbar.start_t + if dt: + pbar.set_postfix(frame=frame_num, fps=f"{frame_num / dt:.2f}") + idet = line["frame"].get("lavfi", {}).get("idet") + # rep = idet["repeated"]["current_frame"] + # single = idet["single"]["current_frame"] + # multi = idet["multiple"]["current_frame"] + # pbar.write(repr((rep, single, multi))) + pbar.update(0) + pbar.close() + ret = proc.wait() + if ret != 0: + exit(ret) + stream = data["stream"] + # 30000/1001 + frame_rate = list(map(int, stream["r_frame_rate"].split("/"))) + frame_rate = Fraction(frame_rate[0], frame_rate[1]) + + frame_num = int(stream["nb_read_frames"]) + cropdetect = data["frame"]["lavfi"]["cropdetect"] + idet = judge(data, frame_num) + crop = ( + int(cropdetect["x"]), + (int(stream["width"]) - int(cropdetect["w"]) - int(cropdetect["x"])), + int(cropdetect["y"]), + (int(stream["height"]) - int(cropdetect["h"]) - int(cropdetect["y"])), + ) + print(f"Cropping: {crop}") + return {"interlacing":idet, "crop":crop} + + +def get_frames(path): + path = path.replace("\\", "/") + proc = SP.Popen( + [ + "ffprobe", + "-probesize", + str(0x7FFFFFFF), + "-analyzeduration", + str(0x7FFFFFFF), + "-v", + "fatal", + "-i", + path, + "-select_streams", + "v:0", + "-show_frames", + "-print_format", + "compact", + ], + stdout=SP.PIPE, + stdin=SP.DEVNULL, + bufsize=0, + encoding="utf8", + ) + for line in proc.stdout: + yield __make_dict(line) + ret = proc.wait() + if ret != 0: + exit(ret) + + +def get_streams(path): + proc = SP.Popen( + [ + "ffprobe", + "-probesize", + str(0x7FFFFFFF), + "-analyzeduration", + str(0x7FFFFFFF), + "-v", + "fatal", + "-i", + path, + "-select_streams", + "v:0", + "-show_streams", + "-show_format", + "-print_format", + "json", + ], + stdout=SP.PIPE, + stdin=SP.DEVNULL, + bufsize=0, + ) + data = json.load(proc.stdout) + ret = proc.wait() + if ret != 0: + exit(ret) + return data["streams"], data["format"] + + +def make_header(file): + return ["DGIndexProjectFile16", "1", os.path.abspath(file)] + + +def make_settings(stream): + pict_size = "x".join(map(str, [stream["width"], stream["height"]])) + frame_rate = list(map(int, stream["r_frame_rate"].split("/"))) + frame_rate = (frame_rate[0] * 1000) // frame_rate[1] + frame_rate = f"{frame_rate} ({stream['r_frame_rate']})" + header = [ + ("Stream_Type", 0), # Elementary Stream + ("MPEG_Type", 2), # MPEG-2 + ("iDCT_Algorithm", 5), # 64-bit IEEE-1180 Reference + ("YUVRGB_Scale", int(stream["color_range"] != "tv")), + ("Luminance_Filter", "0,0"), + ("Clipping", "0,0,0,0"), + ("Aspect_Ratio", stream["display_aspect_ratio"]), + ("Picture_Size", pict_size), + ("Field_Operation", 0), # Honor Pulldown Flags + ("Frame_Rate", frame_rate), + ("Location", "0,0,0,0"), + ] + for k, v in header: + yield f"{k}={v}" + + +def gen_d2v(path): + yield from make_header(path) + yield "" + streams, fmt = get_streams(path) + stream = [s for s in streams if s["codec_type"] == "video"][0] + stream["index"] = str(stream["index"]) + yield from make_settings(stream) + yield "" + line_buffer = [] + frames = get_frames(path) + prog_bar = tqdm( + frames, + total=int(fmt["size"]), + unit_divisor=1024, + unit_scale=True, + unit="iB", + desc="Writing d2v", + leave=False, + ) + cropdetect = None + idet = None + frame_num = 0 + t_start=perf_counter() + for line in prog_bar: + if "frame" not in line: + continue + frame = line["frame"] + if frame["stream_index"] != stream["index"]: + continue + prog_bar.n = min(max(prog_bar.n, int(frame["pkt_pos"])), int(fmt["size"])) + fps=frame_num/(perf_counter()-t_start) + prog_bar.set_postfix(frame=frame_num,fps=f"{fps:.02f}") + prog_bar.update(0) + frame_num += 1 + if frame["pict_type"] == "I" and line_buffer: + yield make_line(line_buffer, stream) + line_buffer.clear() + line_buffer.append(frame) + if line_buffer: + yield make_line(line_buffer, stream) + prog_bar.n = int(fmt["size"]) + prog_bar.update(0) + prog_bar.close() + yield None + +def make_meta(path): + outdir = os.path.dirname(path) + outfile = os.path.splitext(os.path.basename(path))[0] + outfile = os.path.join(outdir, os.path.extsep.join([outfile, "info", "json"])) + if os.path.isfile(outfile): + print(path,"already analyzed, skipping") + return + print("Analyzing", path) + meta = get_meta_interlacing(path) + streams, fmt = get_streams(path) + stream = streams[0] + var = Fraction(int(stream["width"]), int(stream["height"])) + dar = Fraction(*map(int, stream["display_aspect_ratio"].split(":"))) + sar = Fraction(*map(int, stream["sample_aspect_ratio"].split(":"))) + par = sar * dar + meta.update( + { + "par": [par.numerator, par.denominator], + "dar": [dar.numerator, dar.denominator], + "sar": [sar.numerator, sar.denominator], + "var": [var.numerator, var.denominator], + } + ) + print(f"Aspect ratios:") + print(f" Pixel {par}") + print(f" Display {dar}") + print(f" Screen {sar}") + print(f" Video {var}") + with open(outfile, "w") as fh: + json.dump(meta, fh, indent=4) + + +def make_d2v(path): + outdir = os.path.dirname(path) + outfile = os.path.splitext(os.path.basename(path))[0] + outfile = os.path.join(outdir, os.path.extsep.join([outfile, "d2v"])) + outfile_tmp = os.path.extsep.join([outfile, "tmp"]) + if os.path.isfile(outfile): + print(path,"already indexed, skipping") + return + print("Indexing", path) + a, b = ITT.tee(gen_d2v(path)) + next(b) + with open(outfile_tmp, "w") as fh: + for line, next_line in zip(a, b): + fh.write(line) + if next_line is None: # last line, append end marker + fh.write(" ff") + fh.write("\n") + os.rename(outfile_tmp,outfile) + + +if __name__ == "__main__": + for file in ITT.chain.from_iterable(map(glob, sys.argv[1:])): + make_meta(file) + make_d2v(file) diff --git a/ifo_read.h b/ifo_read.h index 27fb306..e0fd89e 100644 --- a/ifo_read.h +++ b/ifo_read.h @@ -1,215 +1,215 @@ -/* - * Copyright (C) 2000, 2001, 2002 Björn Englund , - * Håkan Hjort - * - * This file is part of libdvdread. - * - * libdvdread 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 of the License, or - * (at your option) any later version. - * - * libdvdread 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 libdvdread; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/** - * handle = ifoOpen(dvd, title); - * - * Opens an IFO and reads in all the data for the IFO file corresponding to the - * given title. If title 0 is given, the video manager IFO file is read. - * Returns a handle to a completely parsed structure. - */ -ifo_handle_t *ifoOpen(dvd_reader_t *, int ); - -/** - * handle = ifoOpenVMGI(dvd); - * - * Opens an IFO and reads in _only_ the vmgi_mat data. This call can be used - * together with the calls below to read in each segment of the IFO file on - * demand. - */ -ifo_handle_t *ifoOpenVMGI(dvd_reader_t *); - -/** - * handle = ifoOpenVTSI(dvd, title); - * - * Opens an IFO and reads in _only_ the vtsi_mat data. This call can be used - * together with the calls below to read in each segment of the IFO file on - * demand. - */ -ifo_handle_t *ifoOpenVTSI(dvd_reader_t *, int); - -/** - * ifoClose(ifofile); - * Cleans up the IFO information. This will free all data allocated for the - * substructures. - */ -void ifoClose(ifo_handle_t *); - -/** - * The following functions are for reading only part of the VMGI/VTSI files. - * Returns 1 if the data was successfully read and 0 on error. - */ - -/** - * okay = ifoRead_PLT_MAIT(ifofile); - * - * Read in the Parental Management Information table, filling the - * ifofile->ptl_mait structure and its substructures. This data is only - * located in the video manager information file. This fills the - * ifofile->ptl_mait structure and all its substructures. - */ -int ifoRead_PTL_MAIT(ifo_handle_t *); - -/** - * okay = ifoRead_VTS_ATRT(ifofile); - * - * Read in the attribute table for the main menu vob, filling the - * ifofile->vts_atrt structure and its substructures. Only located in the - * video manager information file. This fills in the ifofile->vts_atrt - * structure and all its substructures. - */ -int ifoRead_VTS_ATRT(ifo_handle_t *); - -/** - * okay = ifoRead_TT_SRPT(ifofile); - * - * Reads the title info for the main menu, filling the ifofile->tt_srpt - * structure and its substructures. This data is only located in the video - * manager information file. This structure is mandatory in the IFO file. - */ -int ifoRead_TT_SRPT(ifo_handle_t *); - -/** - * okay = ifoRead_VTS_PTT_SRPT(ifofile); - * - * Reads in the part of title search pointer table, filling the - * ifofile->vts_ptt_srpt structure and its substructures. This data is only - * located in the video title set information file. This structure is - * mandatory, and must be included in the VTSI file. - */ -int ifoRead_VTS_PTT_SRPT(ifo_handle_t *); - -/** - * okay = ifoRead_FP_PGC(ifofile); - * - * Reads in the first play program chain data, filling the - * ifofile->first_play_pgc structure. This data is only located in the video - * manager information file (VMGI). This structure is optional. - */ -int ifoRead_FP_PGC(ifo_handle_t *); - -/** - * okay = ifoRead_PGCIT(ifofile); - * - * Reads in the program chain information table for the video title set. Fills - * in the ifofile->vts_pgcit structure and its substructures, which includes - * the data for each program chain in the set. This data is only located in - * the video title set information file. This structure is mandatory, and must - * be included in the VTSI file. - */ -int ifoRead_PGCIT(ifo_handle_t *); - -/** - * okay = ifoRead_PGCI_UT(ifofile); - * - * Reads in the menu PGCI unit table for the menu VOB. For the video manager, - * this corresponds to the VIDEO_TS.VOB file, and for each title set, this - * corresponds to the VTS_XX_0.VOB file. This data is located in both the - * video manager and video title set information files. For VMGI files, this - * fills the ifofile->vmgi_pgci_ut structure and all its substructures. For - * VTSI files, this fills the ifofile->vtsm_pgci_ut structure. - */ -int ifoRead_PGCI_UT(ifo_handle_t *); - -/** - * okay = ifoRead_VTS_TMAPT(ifofile); - * - * Reads in the VTS Time Map Table, this data is only located in the video - * title set information file. This fills the ifofile->vts_tmapt structure - * and all its substructures. When pressent enables VOBU level time-based - * seeking for One_Sequential_PGC_Titles. - */ -int ifoRead_VTS_TMAPT(ifo_handle_t *); - -/** - * okay = ifoRead_C_ADT(ifofile); - * - * Reads in the cell address table for the menu VOB. For the video manager, - * this corresponds to the VIDEO_TS.VOB file, and for each title set, this - * corresponds to the VTS_XX_0.VOB file. This data is located in both the - * video manager and video title set information files. For VMGI files, this - * fills the ifofile->vmgm_c_adt structure and all its substructures. For VTSI - * files, this fills the ifofile->vtsm_c_adt structure. - */ -int ifoRead_C_ADT(ifo_handle_t *); - -/** - * okay = ifoRead_TITLE_C_ADT(ifofile); - * - * Reads in the cell address table for the video title set corresponding to - * this IFO file. This data is only located in the video title set information - * file. This structure is mandatory, and must be included in the VTSI file. - * This call fills the ifofile->vts_c_adt structure and its substructures. - */ -int ifoRead_TITLE_C_ADT(ifo_handle_t *); - -/** - * okay = ifoRead_VOBU_ADMAP(ifofile); - * - * Reads in the VOBU address map for the menu VOB. For the video manager, this - * corresponds to the VIDEO_TS.VOB file, and for each title set, this - * corresponds to the VTS_XX_0.VOB file. This data is located in both the - * video manager and video title set information files. For VMGI files, this - * fills the ifofile->vmgm_vobu_admap structure and all its substructures. For - * VTSI files, this fills the ifofile->vtsm_vobu_admap structure. - */ -int ifoRead_VOBU_ADMAP(ifo_handle_t *); - -/** - * okay = ifoRead_TITLE_VOBU_ADMAP(ifofile); - * - * Reads in the VOBU address map for the associated video title set. This data - * is only located in the video title set information file. This structure is - * mandatory, and must be included in the VTSI file. Fills the - * ifofile->vts_vobu_admap structure and its substructures. - */ -int ifoRead_TITLE_VOBU_ADMAP(ifo_handle_t *); - -/** - * okay = ifoRead_TXTDT_MGI(ifofile); - * - * Reads in the text data strings for the DVD. Fills the ifofile->txtdt_mgi - * structure and all its substructures. This data is only located in the video - * manager information file. This structure is mandatory, and must be included - * in the VMGI file. - */ -int ifoRead_TXTDT_MGI(ifo_handle_t *); - -/** - * The following functions are used for freeing parsed sections of the - * ifo_handle_t structure and the allocated substructures. The free calls - * below are safe: they will not mind if you attempt to free part of an IFO - * file which was not read in or which does not exist. - */ -void ifoFree_PTL_MAIT(ifo_handle_t *); -void ifoFree_VTS_ATRT(ifo_handle_t *); -void ifoFree_TT_SRPT(ifo_handle_t *); -void ifoFree_VTS_PTT_SRPT(ifo_handle_t *); -void ifoFree_FP_PGC(ifo_handle_t *); -void ifoFree_PGCIT(ifo_handle_t *); -void ifoFree_PGCI_UT(ifo_handle_t *); -void ifoFree_VTS_TMAPT(ifo_handle_t *); -void ifoFree_C_ADT(ifo_handle_t *); -void ifoFree_TITLE_C_ADT(ifo_handle_t *); -void ifoFree_VOBU_ADMAP(ifo_handle_t *); -void ifoFree_TITLE_VOBU_ADMAP(ifo_handle_t *); -void ifoFree_TXTDT_MGI(ifo_handle_t *); - +/* + * Copyright (C) 2000, 2001, 2002 Björn Englund , + * Håkan Hjort + * + * This file is part of libdvdread. + * + * libdvdread 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 of the License, or + * (at your option) any later version. + * + * libdvdread 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 libdvdread; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * handle = ifoOpen(dvd, title); + * + * Opens an IFO and reads in all the data for the IFO file corresponding to the + * given title. If title 0 is given, the video manager IFO file is read. + * Returns a handle to a completely parsed structure. + */ +ifo_handle_t *ifoOpen(dvd_reader_t *, int ); + +/** + * handle = ifoOpenVMGI(dvd); + * + * Opens an IFO and reads in _only_ the vmgi_mat data. This call can be used + * together with the calls below to read in each segment of the IFO file on + * demand. + */ +ifo_handle_t *ifoOpenVMGI(dvd_reader_t *); + +/** + * handle = ifoOpenVTSI(dvd, title); + * + * Opens an IFO and reads in _only_ the vtsi_mat data. This call can be used + * together with the calls below to read in each segment of the IFO file on + * demand. + */ +ifo_handle_t *ifoOpenVTSI(dvd_reader_t *, int); + +/** + * ifoClose(ifofile); + * Cleans up the IFO information. This will free all data allocated for the + * substructures. + */ +void ifoClose(ifo_handle_t *); + +/** + * The following functions are for reading only part of the VMGI/VTSI files. + * Returns 1 if the data was successfully read and 0 on error. + */ + +/** + * okay = ifoRead_PLT_MAIT(ifofile); + * + * Read in the Parental Management Information table, filling the + * ifofile->ptl_mait structure and its substructures. This data is only + * located in the video manager information file. This fills the + * ifofile->ptl_mait structure and all its substructures. + */ +int ifoRead_PTL_MAIT(ifo_handle_t *); + +/** + * okay = ifoRead_VTS_ATRT(ifofile); + * + * Read in the attribute table for the main menu vob, filling the + * ifofile->vts_atrt structure and its substructures. Only located in the + * video manager information file. This fills in the ifofile->vts_atrt + * structure and all its substructures. + */ +int ifoRead_VTS_ATRT(ifo_handle_t *); + +/** + * okay = ifoRead_TT_SRPT(ifofile); + * + * Reads the title info for the main menu, filling the ifofile->tt_srpt + * structure and its substructures. This data is only located in the video + * manager information file. This structure is mandatory in the IFO file. + */ +int ifoRead_TT_SRPT(ifo_handle_t *); + +/** + * okay = ifoRead_VTS_PTT_SRPT(ifofile); + * + * Reads in the part of title search pointer table, filling the + * ifofile->vts_ptt_srpt structure and its substructures. This data is only + * located in the video title set information file. This structure is + * mandatory, and must be included in the VTSI file. + */ +int ifoRead_VTS_PTT_SRPT(ifo_handle_t *); + +/** + * okay = ifoRead_FP_PGC(ifofile); + * + * Reads in the first play program chain data, filling the + * ifofile->first_play_pgc structure. This data is only located in the video + * manager information file (VMGI). This structure is optional. + */ +int ifoRead_FP_PGC(ifo_handle_t *); + +/** + * okay = ifoRead_PGCIT(ifofile); + * + * Reads in the program chain information table for the video title set. Fills + * in the ifofile->vts_pgcit structure and its substructures, which includes + * the data for each program chain in the set. This data is only located in + * the video title set information file. This structure is mandatory, and must + * be included in the VTSI file. + */ +int ifoRead_PGCIT(ifo_handle_t *); + +/** + * okay = ifoRead_PGCI_UT(ifofile); + * + * Reads in the menu PGCI unit table for the menu VOB. For the video manager, + * this corresponds to the VIDEO_TS.VOB file, and for each title set, this + * corresponds to the VTS_XX_0.VOB file. This data is located in both the + * video manager and video title set information files. For VMGI files, this + * fills the ifofile->vmgi_pgci_ut structure and all its substructures. For + * VTSI files, this fills the ifofile->vtsm_pgci_ut structure. + */ +int ifoRead_PGCI_UT(ifo_handle_t *); + +/** + * okay = ifoRead_VTS_TMAPT(ifofile); + * + * Reads in the VTS Time Map Table, this data is only located in the video + * title set information file. This fills the ifofile->vts_tmapt structure + * and all its substructures. When pressent enables VOBU level time-based + * seeking for One_Sequential_PGC_Titles. + */ +int ifoRead_VTS_TMAPT(ifo_handle_t *); + +/** + * okay = ifoRead_C_ADT(ifofile); + * + * Reads in the cell address table for the menu VOB. For the video manager, + * this corresponds to the VIDEO_TS.VOB file, and for each title set, this + * corresponds to the VTS_XX_0.VOB file. This data is located in both the + * video manager and video title set information files. For VMGI files, this + * fills the ifofile->vmgm_c_adt structure and all its substructures. For VTSI + * files, this fills the ifofile->vtsm_c_adt structure. + */ +int ifoRead_C_ADT(ifo_handle_t *); + +/** + * okay = ifoRead_TITLE_C_ADT(ifofile); + * + * Reads in the cell address table for the video title set corresponding to + * this IFO file. This data is only located in the video title set information + * file. This structure is mandatory, and must be included in the VTSI file. + * This call fills the ifofile->vts_c_adt structure and its substructures. + */ +int ifoRead_TITLE_C_ADT(ifo_handle_t *); + +/** + * okay = ifoRead_VOBU_ADMAP(ifofile); + * + * Reads in the VOBU address map for the menu VOB. For the video manager, this + * corresponds to the VIDEO_TS.VOB file, and for each title set, this + * corresponds to the VTS_XX_0.VOB file. This data is located in both the + * video manager and video title set information files. For VMGI files, this + * fills the ifofile->vmgm_vobu_admap structure and all its substructures. For + * VTSI files, this fills the ifofile->vtsm_vobu_admap structure. + */ +int ifoRead_VOBU_ADMAP(ifo_handle_t *); + +/** + * okay = ifoRead_TITLE_VOBU_ADMAP(ifofile); + * + * Reads in the VOBU address map for the associated video title set. This data + * is only located in the video title set information file. This structure is + * mandatory, and must be included in the VTSI file. Fills the + * ifofile->vts_vobu_admap structure and its substructures. + */ +int ifoRead_TITLE_VOBU_ADMAP(ifo_handle_t *); + +/** + * okay = ifoRead_TXTDT_MGI(ifofile); + * + * Reads in the text data strings for the DVD. Fills the ifofile->txtdt_mgi + * structure and all its substructures. This data is only located in the video + * manager information file. This structure is mandatory, and must be included + * in the VMGI file. + */ +int ifoRead_TXTDT_MGI(ifo_handle_t *); + +/** + * The following functions are used for freeing parsed sections of the + * ifo_handle_t structure and the allocated substructures. The free calls + * below are safe: they will not mind if you attempt to free part of an IFO + * file which was not read in or which does not exist. + */ +void ifoFree_PTL_MAIT(ifo_handle_t *); +void ifoFree_VTS_ATRT(ifo_handle_t *); +void ifoFree_TT_SRPT(ifo_handle_t *); +void ifoFree_VTS_PTT_SRPT(ifo_handle_t *); +void ifoFree_FP_PGC(ifo_handle_t *); +void ifoFree_PGCIT(ifo_handle_t *); +void ifoFree_PGCI_UT(ifo_handle_t *); +void ifoFree_VTS_TMAPT(ifo_handle_t *); +void ifoFree_C_ADT(ifo_handle_t *); +void ifoFree_TITLE_C_ADT(ifo_handle_t *); +void ifoFree_VOBU_ADMAP(ifo_handle_t *); +void ifoFree_TITLE_VOBU_ADMAP(ifo_handle_t *); +void ifoFree_TXTDT_MGI(ifo_handle_t *); + diff --git a/ifo_types.h b/ifo_types.h index 9a5834f..d3b2ce9 100644 --- a/ifo_types.h +++ b/ifo_types.h @@ -1,683 +1,683 @@ - -/** - * Common - * - * The following structures are used in both the VMGI and VTSI. - */ - - -/** - * DVD Time Information. - */ -typedef struct { - uint8_t hour; - uint8_t minute; - uint8_t second; - uint8_t frame_u; /* The two high bits are the frame rate. */ -} dvd_time_t; - -/** - * Type to store per-command data. - */ -typedef struct { - uint8_t bytes[8]; -} vm_cmd_t; -#define COMMAND_DATA_SIZE 8U - - -/** - * Video Attributes. - */ -typedef struct { - unsigned char mpeg_version : 2; - unsigned char video_format : 2; - unsigned char display_aspect_ratio : 2; - unsigned char permitted_df : 2; - - unsigned char line21_cc_1 : 1; - unsigned char line21_cc_2 : 1; - unsigned char unknown1 : 1; - unsigned char bit_rate : 1; - - unsigned char picture_size : 2; - unsigned char letterboxed : 1; - unsigned char film_mode : 1; -} video_attr_t; - -/** - * Audio Attributes. - */ -typedef struct { - unsigned char audio_format : 3; - unsigned char multichannel_extension : 1; - unsigned char lang_type : 2; - unsigned char application_mode : 2; - - unsigned char quantization : 2; - unsigned char sample_frequency : 2; - unsigned char unknown1 : 1; - unsigned char channels : 3; - uint16_t lang_code; - uint8_t lang_extension; - uint8_t code_extension; - uint8_t unknown3; - union { - struct { - unsigned char unknown4 : 1; - unsigned char channel_assignment : 3; - unsigned char version : 2; - unsigned char mc_intro : 1; /* probably 0: true, 1:false */ - unsigned char mode : 1; /* Karaoke mode 0: solo 1: duet */ - } karaoke; - struct { - unsigned char unknown5 : 4; - unsigned char dolby_encoded : 1; /* suitable for surround decoding */ - unsigned char unknown6 : 3; - } surround; - } app_info; -} audio_attr_t; - - -/** - * MultiChannel Extension - */ -typedef struct { - unsigned int zero1 : 7; - unsigned int ach0_gme : 1; - - unsigned int zero2 : 7; - unsigned int ach1_gme : 1; - - unsigned int zero3 : 4; - unsigned int ach2_gv1e : 1; - unsigned int ach2_gv2e : 1; - unsigned int ach2_gm1e : 1; - unsigned int ach2_gm2e : 1; - - unsigned int zero4 : 4; - unsigned int ach3_gv1e : 1; - unsigned int ach3_gv2e : 1; - unsigned int ach3_gmAe : 1; - unsigned int ach3_se2e : 1; - - unsigned int zero5 : 4; - unsigned int ach4_gv1e : 1; - unsigned int ach4_gv2e : 1; - unsigned int ach4_gmBe : 1; - unsigned int ach4_seBe : 1; - uint8_t zero6[19]; -} multichannel_ext_t; - - -/** - * Subpicture Attributes. - */ -typedef struct { - /* - * type: 0 not specified - * 1 language - * 2 other - * coding mode: 0 run length - * 1 extended - * 2 other - * language: indicates language if type == 1 - * lang extension: if type == 1 contains the lang extension - */ - unsigned char code_mode : 3; - unsigned char zero1 : 3; - unsigned char type : 2; - uint8_t zero2; - uint16_t lang_code; - uint8_t lang_extension; - uint8_t code_extension; -} subp_attr_t; - - - -/** - * PGC Command Table. - */ -typedef struct { - uint16_t nr_of_pre; - uint16_t nr_of_post; - uint16_t nr_of_cell; - uint16_t zero_1; - vm_cmd_t *pre_cmds; - vm_cmd_t *post_cmds; - vm_cmd_t *cell_cmds; -} pgc_command_tbl_t; -#define PGC_COMMAND_TBL_SIZE 8U - -/** - * PGC Program Map - */ -typedef uint8_t pgc_program_map_t; - -/** - * Cell Playback Information. - */ -typedef struct { - unsigned int block_mode : 2; - unsigned int block_type : 2; - unsigned int seamless_play : 1; - unsigned int interleaved : 1; - unsigned int stc_discontinuity: 1; - unsigned int seamless_angle : 1; - unsigned int zero_1 : 1; - unsigned int playback_mode : 1; /**< When set, enter StillMode after each VOBU */ - unsigned int restricted : 1; /**< ?? drop out of fastforward? */ - unsigned int cell_type : 5; /** for karaoke, reserved otherwise */ - uint8_t still_time; - uint8_t cell_cmd_nr; - dvd_time_t playback_time; - uint32_t first_sector; - uint32_t first_ilvu_end_sector; - uint32_t last_vobu_start_sector; - uint32_t last_sector; -} cell_playback_t; - -#define BLOCK_TYPE_NONE 0x0 -#define BLOCK_TYPE_ANGLE_BLOCK 0x1 - -#define BLOCK_MODE_NOT_IN_BLOCK 0x0 -#define BLOCK_MODE_FIRST_CELL 0x1 -#define BLOCK_MODE_IN_BLOCK 0x2 -#define BLOCK_MODE_LAST_CELL 0x3 - -/** - * Cell Position Information. - */ -typedef struct { - uint16_t vob_id_nr; - uint8_t zero_1; - uint8_t cell_nr; -} cell_position_t; - -/** - * User Operations. - */ -typedef struct { - unsigned int zero : 7; /* 25-31 */ - unsigned int video_pres_mode_change : 1; /* 24 */ - - unsigned int karaoke_audio_pres_mode_change : 1; /* 23 */ - unsigned int angle_change : 1; - unsigned int subpic_stream_change : 1; - unsigned int audio_stream_change : 1; - unsigned int pause_on : 1; - unsigned int still_off : 1; - unsigned int button_select_or_activate : 1; - unsigned int resume : 1; /* 16 */ - - unsigned int chapter_menu_call : 1; /* 15 */ - unsigned int angle_menu_call : 1; - unsigned int audio_menu_call : 1; - unsigned int subpic_menu_call : 1; - unsigned int root_menu_call : 1; - unsigned int title_menu_call : 1; - unsigned int backward_scan : 1; - unsigned int forward_scan : 1; /* 8 */ - - unsigned int next_pg_search : 1; /* 7 */ - unsigned int prev_or_top_pg_search : 1; - unsigned int time_or_chapter_search : 1; - unsigned int go_up : 1; - unsigned int stop : 1; - unsigned int title_play : 1; - unsigned int chapter_search_or_play : 1; - unsigned int title_or_time_play : 1; /* 0 */ -} user_ops_t; - -/** - * Program Chain Information. - */ -typedef struct { - uint16_t zero_1; - uint8_t nr_of_programs; - uint8_t nr_of_cells; - dvd_time_t playback_time; - user_ops_t prohibited_ops; - uint16_t audio_control[8]; /* New type? */ - uint32_t subp_control[32]; /* New type? */ - uint16_t next_pgc_nr; - uint16_t prev_pgc_nr; - uint16_t goup_pgc_nr; - uint8_t pg_playback_mode; - uint8_t still_time; - uint32_t palette[16]; /* New type struct {zero_1, Y, Cr, Cb} ? */ - uint16_t command_tbl_offset; - uint16_t program_map_offset; - uint16_t cell_playback_offset; - uint16_t cell_position_offset; - pgc_command_tbl_t *command_tbl; - pgc_program_map_t *program_map; - cell_playback_t *cell_playback; - cell_position_t *cell_position; - int ref_count; -} pgc_t; -#define PGC_SIZE 236U - -/** - * Program Chain Information Search Pointer. - */ -typedef struct { - uint8_t entry_id; - unsigned int block_mode : 2; - unsigned int block_type : 2; - unsigned int zero_1 : 4; - uint16_t ptl_id_mask; - uint32_t pgc_start_byte; - pgc_t *pgc; -} pgci_srp_t; -#define PGCI_SRP_SIZE 8U - -/** - * Program Chain Information Table. - */ -typedef struct { - uint16_t nr_of_pgci_srp; - uint16_t zero_1; - uint32_t last_byte; - pgci_srp_t *pgci_srp; - int ref_count; -} pgcit_t; -#define PGCIT_SIZE 8U - -/** - * Menu PGCI Language Unit. - */ -typedef struct { - uint16_t lang_code; - uint8_t lang_extension; - uint8_t exists; - uint32_t lang_start_byte; - pgcit_t *pgcit; -} pgci_lu_t; -#define PGCI_LU_SIZE 8U - -/** - * Menu PGCI Unit Table. - */ -typedef struct { - uint16_t nr_of_lus; - uint16_t zero_1; - uint32_t last_byte; - pgci_lu_t *lu; -} pgci_ut_t; -#define PGCI_UT_SIZE 8U - -/** - * Cell Address Information. - */ -typedef struct { - uint16_t vob_id; - uint8_t cell_id; - uint8_t zero_1; - uint32_t start_sector; - uint32_t last_sector; -} cell_adr_t; - -/** - * Cell Address Table. - */ -typedef struct { - uint16_t nr_of_vobs; /* VOBs */ - uint16_t zero_1; - uint32_t last_byte; - cell_adr_t *cell_adr_table; /* No explicit size given. */ -} c_adt_t; -#define C_ADT_SIZE 8U - -/** - * VOBU Address Map. - */ -typedef struct { - uint32_t last_byte; - uint32_t *vobu_start_sectors; -} vobu_admap_t; -#define VOBU_ADMAP_SIZE 4U - - - - -/** - * VMGI - * - * The following structures relate to the Video Manager. - */ - -/** - * Video Manager Information Management Table. - */ -typedef struct { - char vmg_identifier[12]; - uint32_t vmg_last_sector; - uint8_t zero_1[12]; - uint32_t vmgi_last_sector; - uint8_t zero_2; - uint8_t specification_version; - uint32_t vmg_category; - uint16_t vmg_nr_of_volumes; - uint16_t vmg_this_volume_nr; - uint8_t disc_side; - uint8_t zero_3[19]; - uint16_t vmg_nr_of_title_sets; /* Number of VTSs. */ - char provider_identifier[32]; - uint64_t vmg_pos_code; - uint8_t zero_4[24]; - uint32_t vmgi_last_byte; - uint32_t first_play_pgc; - uint8_t zero_5[56]; - uint32_t vmgm_vobs; /* sector */ - uint32_t tt_srpt; /* sector */ - uint32_t vmgm_pgci_ut; /* sector */ - uint32_t ptl_mait; /* sector */ - uint32_t vts_atrt; /* sector */ - uint32_t txtdt_mgi; /* sector */ - uint32_t vmgm_c_adt; /* sector */ - uint32_t vmgm_vobu_admap; /* sector */ - uint8_t zero_6[32]; - - video_attr_t vmgm_video_attr; - uint8_t zero_7; - uint8_t nr_of_vmgm_audio_streams; /* should be 0 or 1 */ - audio_attr_t vmgm_audio_attr; - audio_attr_t zero_8[7]; - uint8_t zero_9[17]; - uint8_t nr_of_vmgm_subp_streams; /* should be 0 or 1 */ - subp_attr_t vmgm_subp_attr; - subp_attr_t zero_10[27]; /* XXX: how much 'padding' here? */ -} vmgi_mat_t; - -typedef struct { - unsigned int zero_1 : 1; - unsigned int multi_or_random_pgc_title : 1; /* 0: one sequential pgc title */ - unsigned int jlc_exists_in_cell_cmd : 1; - unsigned int jlc_exists_in_prepost_cmd : 1; - unsigned int jlc_exists_in_button_cmd : 1; - unsigned int jlc_exists_in_tt_dom : 1; - unsigned int chapter_search_or_play : 1; /* UOP 1 */ - unsigned int title_or_time_play : 1; /* UOP 0 */ -} playback_type_t; - -/** - * Title Information. - */ -typedef struct { - playback_type_t pb_ty; - uint8_t nr_of_angles; - uint16_t nr_of_ptts; - uint16_t parental_id; - uint8_t title_set_nr; - uint8_t vts_ttn; - uint32_t title_set_sector; -} title_info_t; - -/** - * PartOfTitle Search Pointer Table. - */ -typedef struct { - uint16_t nr_of_srpts; - uint16_t zero_1; - uint32_t last_byte; - title_info_t *title; -} tt_srpt_t; -#define TT_SRPT_SIZE 8U - - -/** - * Parental Management Information Unit Table. - * Level 1 (US: G), ..., 7 (US: NC-17), 8 - */ -#define PTL_MAIT_NUM_LEVEL 8 -typedef uint16_t pf_level_t[PTL_MAIT_NUM_LEVEL]; - -/** - * Parental Management Information Unit Table. - */ -typedef struct { - uint16_t country_code; - uint16_t zero_1; - uint16_t pf_ptl_mai_start_byte; - uint16_t zero_2; - pf_level_t *pf_ptl_mai; /* table of (nr_of_vtss + 1), video_ts is first */ -} ptl_mait_country_t; -#define PTL_MAIT_COUNTRY_SIZE 8U - -/** - * Parental Management Information Table. - */ -typedef struct { - uint16_t nr_of_countries; - uint16_t nr_of_vtss; - uint32_t last_byte; - ptl_mait_country_t *countries; -} ptl_mait_t; -#define PTL_MAIT_SIZE 8U - -/** - * Video Title Set Attributes. - */ -typedef struct { - uint32_t last_byte; - uint32_t vts_cat; - - video_attr_t vtsm_vobs_attr; - uint8_t zero_1; - uint8_t nr_of_vtsm_audio_streams; /* should be 0 or 1 */ - audio_attr_t vtsm_audio_attr; - audio_attr_t zero_2[7]; - uint8_t zero_3[16]; - uint8_t zero_4; - uint8_t nr_of_vtsm_subp_streams; /* should be 0 or 1 */ - subp_attr_t vtsm_subp_attr; - subp_attr_t zero_5[27]; - - uint8_t zero_6[2]; - - video_attr_t vtstt_vobs_video_attr; - uint8_t zero_7; - uint8_t nr_of_vtstt_audio_streams; - audio_attr_t vtstt_audio_attr[8]; - uint8_t zero_8[16]; - uint8_t zero_9; - uint8_t nr_of_vtstt_subp_streams; - subp_attr_t vtstt_subp_attr[32]; -} vts_attributes_t; -#define VTS_ATTRIBUTES_SIZE 542U -#define VTS_ATTRIBUTES_MIN_SIZE 356U - -/** - * Video Title Set Attribute Table. - */ -typedef struct { - uint16_t nr_of_vtss; - uint16_t zero_1; - uint32_t last_byte; - vts_attributes_t *vts; - uint32_t *vts_atrt_offsets; /* offsets table for each vts_attributes */ -} vts_atrt_t; -#define VTS_ATRT_SIZE 8U - -/** - * Text Data. (Incomplete) - */ -typedef struct { - uint32_t last_byte; /* offsets are relative here */ - uint16_t offsets[100]; /* == nr_of_srpts + 1 (first is disc title) */ -} txtdt_t; - -/** - * Text Data Language Unit. (Incomplete) - */ -typedef struct { - uint16_t lang_code; - uint8_t zero_1; - uint8_t char_set; /* 0x00 reserved Unicode, 0x01 ISO 646, 0x10 JIS Roman & JIS Kanji, 0x11 ISO 8859-1, 0x12 Shift JIS Kanji */ - uint32_t txtdt_start_byte; /* prt, rel start of vmg_txtdt_mgi */ - txtdt_t *txtdt; -} txtdt_lu_t; -#define TXTDT_LU_SIZE 8U - -/** - * Text Data Manager Information. (Incomplete) - */ -typedef struct { - char disc_name[12]; - uint16_t zero_1; - uint16_t nr_of_language_units; - uint32_t last_byte; - txtdt_lu_t *lu; -} txtdt_mgi_t; -#define TXTDT_MGI_SIZE 20U - - -/** - * VTS - * - * Structures relating to the Video Title Set (VTS). - */ - -/** - * Video Title Set Information Management Table. - */ -typedef struct { - char vts_identifier[12]; - uint32_t vts_last_sector; - uint8_t zero_1[12]; - uint32_t vtsi_last_sector; - uint8_t zero_2; - uint8_t specification_version; - uint32_t vts_category; - uint16_t zero_3; - uint16_t zero_4; - uint8_t zero_5; - uint8_t zero_6[19]; - uint16_t zero_7; - uint8_t zero_8[32]; - uint64_t zero_9; - uint8_t zero_10[24]; - uint32_t vtsi_last_byte; - uint32_t zero_11; - uint8_t zero_12[56]; - uint32_t vtsm_vobs; /* sector */ - uint32_t vtstt_vobs; /* sector */ - uint32_t vts_ptt_srpt; /* sector */ - uint32_t vts_pgcit; /* sector */ - uint32_t vtsm_pgci_ut; /* sector */ - uint32_t vts_tmapt; /* sector */ - uint32_t vtsm_c_adt; /* sector */ - uint32_t vtsm_vobu_admap; /* sector */ - uint32_t vts_c_adt; /* sector */ - uint32_t vts_vobu_admap; /* sector */ - uint8_t zero_13[24]; - - video_attr_t vtsm_video_attr; - uint8_t zero_14; - uint8_t nr_of_vtsm_audio_streams; /* should be 0 or 1 */ - audio_attr_t vtsm_audio_attr; - audio_attr_t zero_15[7]; - uint8_t zero_16[17]; - uint8_t nr_of_vtsm_subp_streams; /* should be 0 or 1 */ - subp_attr_t vtsm_subp_attr; - subp_attr_t zero_17[27]; - uint8_t zero_18[2]; - - video_attr_t vts_video_attr; - uint8_t zero_19; - uint8_t nr_of_vts_audio_streams; - audio_attr_t vts_audio_attr[8]; - uint8_t zero_20[17]; - uint8_t nr_of_vts_subp_streams; - subp_attr_t vts_subp_attr[32]; - uint16_t zero_21; - multichannel_ext_t vts_mu_audio_attr[8]; - /* XXX: how much 'padding' here, if any? */ -} vtsi_mat_t; - -/** - * PartOfTitle Unit Information. - */ -typedef struct { - uint16_t pgcn; - uint16_t pgn; -} ptt_info_t; - -/** - * PartOfTitle Information. - */ -typedef struct { - uint16_t nr_of_ptts; - ptt_info_t *ptt; -} ttu_t; - -/** - * PartOfTitle Search Pointer Table. - */ -typedef struct { - uint16_t nr_of_srpts; - uint16_t zero_1; - uint32_t last_byte; - ttu_t *title; - uint32_t *ttu_offset; /* offset table for each ttu */ -} vts_ptt_srpt_t; -#define VTS_PTT_SRPT_SIZE 8U - - -/** - * Time Map Entry. - */ -/* Should this be bit field at all or just the uint32_t? */ -typedef uint32_t map_ent_t; - -/** - * Time Map. - */ -typedef struct { - uint8_t tmu; /* Time unit, in seconds */ - uint8_t zero_1; - uint16_t nr_of_entries; - map_ent_t *map_ent; -} vts_tmap_t; -#define VTS_TMAP_SIZE 4U - -/** - * Time Map Table. - */ -typedef struct { - uint16_t nr_of_tmaps; - uint16_t zero_1; - uint32_t last_byte; - vts_tmap_t *tmap; - uint32_t *tmap_offset; /* offset table for each tmap */ -} vts_tmapt_t; -#define VTS_TMAPT_SIZE 8U - -/** - * The following structure defines an IFO file. The structure is divided into - * two parts, the VMGI, or Video Manager Information, which is read from the - * VIDEO_TS.[IFO,BUP] file, and the VTSI, or Video Title Set Information, which - * is read in from the VTS_XX_0.[IFO,BUP] files. - */ -typedef struct { - /* VMGI */ - vmgi_mat_t *vmgi_mat; - tt_srpt_t *tt_srpt; - pgc_t *first_play_pgc; - ptl_mait_t *ptl_mait; - vts_atrt_t *vts_atrt; - txtdt_mgi_t *txtdt_mgi; - - /* Common */ - pgci_ut_t *pgci_ut; - c_adt_t *menu_c_adt; - vobu_admap_t *menu_vobu_admap; - - /* VTSI */ - vtsi_mat_t *vtsi_mat; - vts_ptt_srpt_t *vts_ptt_srpt; - pgcit_t *vts_pgcit; - vts_tmapt_t *vts_tmapt; - c_adt_t *vts_c_adt; - vobu_admap_t *vts_vobu_admap; -} ifo_handle_t; + +/** + * Common + * + * The following structures are used in both the VMGI and VTSI. + */ + + +/** + * DVD Time Information. + */ +typedef struct { + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t frame_u; /* The two high bits are the frame rate. */ +} dvd_time_t; + +/** + * Type to store per-command data. + */ +typedef struct { + uint8_t bytes[8]; +} vm_cmd_t; +#define COMMAND_DATA_SIZE 8U + + +/** + * Video Attributes. + */ +typedef struct { + unsigned char mpeg_version : 2; + unsigned char video_format : 2; + unsigned char display_aspect_ratio : 2; + unsigned char permitted_df : 2; + + unsigned char line21_cc_1 : 1; + unsigned char line21_cc_2 : 1; + unsigned char unknown1 : 1; + unsigned char bit_rate : 1; + + unsigned char picture_size : 2; + unsigned char letterboxed : 1; + unsigned char film_mode : 1; +} video_attr_t; + +/** + * Audio Attributes. + */ +typedef struct { + unsigned char audio_format : 3; + unsigned char multichannel_extension : 1; + unsigned char lang_type : 2; + unsigned char application_mode : 2; + + unsigned char quantization : 2; + unsigned char sample_frequency : 2; + unsigned char unknown1 : 1; + unsigned char channels : 3; + uint16_t lang_code; + uint8_t lang_extension; + uint8_t code_extension; + uint8_t unknown3; + union { + struct { + unsigned char unknown4 : 1; + unsigned char channel_assignment : 3; + unsigned char version : 2; + unsigned char mc_intro : 1; /* probably 0: true, 1:false */ + unsigned char mode : 1; /* Karaoke mode 0: solo 1: duet */ + } karaoke; + struct { + unsigned char unknown5 : 4; + unsigned char dolby_encoded : 1; /* suitable for surround decoding */ + unsigned char unknown6 : 3; + } surround; + } app_info; +} audio_attr_t; + + +/** + * MultiChannel Extension + */ +typedef struct { + unsigned int zero1 : 7; + unsigned int ach0_gme : 1; + + unsigned int zero2 : 7; + unsigned int ach1_gme : 1; + + unsigned int zero3 : 4; + unsigned int ach2_gv1e : 1; + unsigned int ach2_gv2e : 1; + unsigned int ach2_gm1e : 1; + unsigned int ach2_gm2e : 1; + + unsigned int zero4 : 4; + unsigned int ach3_gv1e : 1; + unsigned int ach3_gv2e : 1; + unsigned int ach3_gmAe : 1; + unsigned int ach3_se2e : 1; + + unsigned int zero5 : 4; + unsigned int ach4_gv1e : 1; + unsigned int ach4_gv2e : 1; + unsigned int ach4_gmBe : 1; + unsigned int ach4_seBe : 1; + uint8_t zero6[19]; +} multichannel_ext_t; + + +/** + * Subpicture Attributes. + */ +typedef struct { + /* + * type: 0 not specified + * 1 language + * 2 other + * coding mode: 0 run length + * 1 extended + * 2 other + * language: indicates language if type == 1 + * lang extension: if type == 1 contains the lang extension + */ + unsigned char code_mode : 3; + unsigned char zero1 : 3; + unsigned char type : 2; + uint8_t zero2; + uint16_t lang_code; + uint8_t lang_extension; + uint8_t code_extension; +} subp_attr_t; + + + +/** + * PGC Command Table. + */ +typedef struct { + uint16_t nr_of_pre; + uint16_t nr_of_post; + uint16_t nr_of_cell; + uint16_t zero_1; + vm_cmd_t *pre_cmds; + vm_cmd_t *post_cmds; + vm_cmd_t *cell_cmds; +} pgc_command_tbl_t; +#define PGC_COMMAND_TBL_SIZE 8U + +/** + * PGC Program Map + */ +typedef uint8_t pgc_program_map_t; + +/** + * Cell Playback Information. + */ +typedef struct { + unsigned int block_mode : 2; + unsigned int block_type : 2; + unsigned int seamless_play : 1; + unsigned int interleaved : 1; + unsigned int stc_discontinuity: 1; + unsigned int seamless_angle : 1; + unsigned int zero_1 : 1; + unsigned int playback_mode : 1; /**< When set, enter StillMode after each VOBU */ + unsigned int restricted : 1; /**< ?? drop out of fastforward? */ + unsigned int cell_type : 5; /** for karaoke, reserved otherwise */ + uint8_t still_time; + uint8_t cell_cmd_nr; + dvd_time_t playback_time; + uint32_t first_sector; + uint32_t first_ilvu_end_sector; + uint32_t last_vobu_start_sector; + uint32_t last_sector; +} cell_playback_t; + +#define BLOCK_TYPE_NONE 0x0 +#define BLOCK_TYPE_ANGLE_BLOCK 0x1 + +#define BLOCK_MODE_NOT_IN_BLOCK 0x0 +#define BLOCK_MODE_FIRST_CELL 0x1 +#define BLOCK_MODE_IN_BLOCK 0x2 +#define BLOCK_MODE_LAST_CELL 0x3 + +/** + * Cell Position Information. + */ +typedef struct { + uint16_t vob_id_nr; + uint8_t zero_1; + uint8_t cell_nr; +} cell_position_t; + +/** + * User Operations. + */ +typedef struct { + unsigned int zero : 7; /* 25-31 */ + unsigned int video_pres_mode_change : 1; /* 24 */ + + unsigned int karaoke_audio_pres_mode_change : 1; /* 23 */ + unsigned int angle_change : 1; + unsigned int subpic_stream_change : 1; + unsigned int audio_stream_change : 1; + unsigned int pause_on : 1; + unsigned int still_off : 1; + unsigned int button_select_or_activate : 1; + unsigned int resume : 1; /* 16 */ + + unsigned int chapter_menu_call : 1; /* 15 */ + unsigned int angle_menu_call : 1; + unsigned int audio_menu_call : 1; + unsigned int subpic_menu_call : 1; + unsigned int root_menu_call : 1; + unsigned int title_menu_call : 1; + unsigned int backward_scan : 1; + unsigned int forward_scan : 1; /* 8 */ + + unsigned int next_pg_search : 1; /* 7 */ + unsigned int prev_or_top_pg_search : 1; + unsigned int time_or_chapter_search : 1; + unsigned int go_up : 1; + unsigned int stop : 1; + unsigned int title_play : 1; + unsigned int chapter_search_or_play : 1; + unsigned int title_or_time_play : 1; /* 0 */ +} user_ops_t; + +/** + * Program Chain Information. + */ +typedef struct { + uint16_t zero_1; + uint8_t nr_of_programs; + uint8_t nr_of_cells; + dvd_time_t playback_time; + user_ops_t prohibited_ops; + uint16_t audio_control[8]; /* New type? */ + uint32_t subp_control[32]; /* New type? */ + uint16_t next_pgc_nr; + uint16_t prev_pgc_nr; + uint16_t goup_pgc_nr; + uint8_t pg_playback_mode; + uint8_t still_time; + uint32_t palette[16]; /* New type struct {zero_1, Y, Cr, Cb} ? */ + uint16_t command_tbl_offset; + uint16_t program_map_offset; + uint16_t cell_playback_offset; + uint16_t cell_position_offset; + pgc_command_tbl_t *command_tbl; + pgc_program_map_t *program_map; + cell_playback_t *cell_playback; + cell_position_t *cell_position; + int ref_count; +} pgc_t; +#define PGC_SIZE 236U + +/** + * Program Chain Information Search Pointer. + */ +typedef struct { + uint8_t entry_id; + unsigned int block_mode : 2; + unsigned int block_type : 2; + unsigned int zero_1 : 4; + uint16_t ptl_id_mask; + uint32_t pgc_start_byte; + pgc_t *pgc; +} pgci_srp_t; +#define PGCI_SRP_SIZE 8U + +/** + * Program Chain Information Table. + */ +typedef struct { + uint16_t nr_of_pgci_srp; + uint16_t zero_1; + uint32_t last_byte; + pgci_srp_t *pgci_srp; + int ref_count; +} pgcit_t; +#define PGCIT_SIZE 8U + +/** + * Menu PGCI Language Unit. + */ +typedef struct { + uint16_t lang_code; + uint8_t lang_extension; + uint8_t exists; + uint32_t lang_start_byte; + pgcit_t *pgcit; +} pgci_lu_t; +#define PGCI_LU_SIZE 8U + +/** + * Menu PGCI Unit Table. + */ +typedef struct { + uint16_t nr_of_lus; + uint16_t zero_1; + uint32_t last_byte; + pgci_lu_t *lu; +} pgci_ut_t; +#define PGCI_UT_SIZE 8U + +/** + * Cell Address Information. + */ +typedef struct { + uint16_t vob_id; + uint8_t cell_id; + uint8_t zero_1; + uint32_t start_sector; + uint32_t last_sector; +} cell_adr_t; + +/** + * Cell Address Table. + */ +typedef struct { + uint16_t nr_of_vobs; /* VOBs */ + uint16_t zero_1; + uint32_t last_byte; + cell_adr_t *cell_adr_table; /* No explicit size given. */ +} c_adt_t; +#define C_ADT_SIZE 8U + +/** + * VOBU Address Map. + */ +typedef struct { + uint32_t last_byte; + uint32_t *vobu_start_sectors; +} vobu_admap_t; +#define VOBU_ADMAP_SIZE 4U + + + + +/** + * VMGI + * + * The following structures relate to the Video Manager. + */ + +/** + * Video Manager Information Management Table. + */ +typedef struct { + char vmg_identifier[12]; + uint32_t vmg_last_sector; + uint8_t zero_1[12]; + uint32_t vmgi_last_sector; + uint8_t zero_2; + uint8_t specification_version; + uint32_t vmg_category; + uint16_t vmg_nr_of_volumes; + uint16_t vmg_this_volume_nr; + uint8_t disc_side; + uint8_t zero_3[19]; + uint16_t vmg_nr_of_title_sets; /* Number of VTSs. */ + char provider_identifier[32]; + uint64_t vmg_pos_code; + uint8_t zero_4[24]; + uint32_t vmgi_last_byte; + uint32_t first_play_pgc; + uint8_t zero_5[56]; + uint32_t vmgm_vobs; /* sector */ + uint32_t tt_srpt; /* sector */ + uint32_t vmgm_pgci_ut; /* sector */ + uint32_t ptl_mait; /* sector */ + uint32_t vts_atrt; /* sector */ + uint32_t txtdt_mgi; /* sector */ + uint32_t vmgm_c_adt; /* sector */ + uint32_t vmgm_vobu_admap; /* sector */ + uint8_t zero_6[32]; + + video_attr_t vmgm_video_attr; + uint8_t zero_7; + uint8_t nr_of_vmgm_audio_streams; /* should be 0 or 1 */ + audio_attr_t vmgm_audio_attr; + audio_attr_t zero_8[7]; + uint8_t zero_9[17]; + uint8_t nr_of_vmgm_subp_streams; /* should be 0 or 1 */ + subp_attr_t vmgm_subp_attr; + subp_attr_t zero_10[27]; /* XXX: how much 'padding' here? */ +} vmgi_mat_t; + +typedef struct { + unsigned int zero_1 : 1; + unsigned int multi_or_random_pgc_title : 1; /* 0: one sequential pgc title */ + unsigned int jlc_exists_in_cell_cmd : 1; + unsigned int jlc_exists_in_prepost_cmd : 1; + unsigned int jlc_exists_in_button_cmd : 1; + unsigned int jlc_exists_in_tt_dom : 1; + unsigned int chapter_search_or_play : 1; /* UOP 1 */ + unsigned int title_or_time_play : 1; /* UOP 0 */ +} playback_type_t; + +/** + * Title Information. + */ +typedef struct { + playback_type_t pb_ty; + uint8_t nr_of_angles; + uint16_t nr_of_ptts; + uint16_t parental_id; + uint8_t title_set_nr; + uint8_t vts_ttn; + uint32_t title_set_sector; +} title_info_t; + +/** + * PartOfTitle Search Pointer Table. + */ +typedef struct { + uint16_t nr_of_srpts; + uint16_t zero_1; + uint32_t last_byte; + title_info_t *title; +} tt_srpt_t; +#define TT_SRPT_SIZE 8U + + +/** + * Parental Management Information Unit Table. + * Level 1 (US: G), ..., 7 (US: NC-17), 8 + */ +#define PTL_MAIT_NUM_LEVEL 8 +typedef uint16_t pf_level_t[PTL_MAIT_NUM_LEVEL]; + +/** + * Parental Management Information Unit Table. + */ +typedef struct { + uint16_t country_code; + uint16_t zero_1; + uint16_t pf_ptl_mai_start_byte; + uint16_t zero_2; + pf_level_t *pf_ptl_mai; /* table of (nr_of_vtss + 1), video_ts is first */ +} ptl_mait_country_t; +#define PTL_MAIT_COUNTRY_SIZE 8U + +/** + * Parental Management Information Table. + */ +typedef struct { + uint16_t nr_of_countries; + uint16_t nr_of_vtss; + uint32_t last_byte; + ptl_mait_country_t *countries; +} ptl_mait_t; +#define PTL_MAIT_SIZE 8U + +/** + * Video Title Set Attributes. + */ +typedef struct { + uint32_t last_byte; + uint32_t vts_cat; + + video_attr_t vtsm_vobs_attr; + uint8_t zero_1; + uint8_t nr_of_vtsm_audio_streams; /* should be 0 or 1 */ + audio_attr_t vtsm_audio_attr; + audio_attr_t zero_2[7]; + uint8_t zero_3[16]; + uint8_t zero_4; + uint8_t nr_of_vtsm_subp_streams; /* should be 0 or 1 */ + subp_attr_t vtsm_subp_attr; + subp_attr_t zero_5[27]; + + uint8_t zero_6[2]; + + video_attr_t vtstt_vobs_video_attr; + uint8_t zero_7; + uint8_t nr_of_vtstt_audio_streams; + audio_attr_t vtstt_audio_attr[8]; + uint8_t zero_8[16]; + uint8_t zero_9; + uint8_t nr_of_vtstt_subp_streams; + subp_attr_t vtstt_subp_attr[32]; +} vts_attributes_t; +#define VTS_ATTRIBUTES_SIZE 542U +#define VTS_ATTRIBUTES_MIN_SIZE 356U + +/** + * Video Title Set Attribute Table. + */ +typedef struct { + uint16_t nr_of_vtss; + uint16_t zero_1; + uint32_t last_byte; + vts_attributes_t *vts; + uint32_t *vts_atrt_offsets; /* offsets table for each vts_attributes */ +} vts_atrt_t; +#define VTS_ATRT_SIZE 8U + +/** + * Text Data. (Incomplete) + */ +typedef struct { + uint32_t last_byte; /* offsets are relative here */ + uint16_t offsets[100]; /* == nr_of_srpts + 1 (first is disc title) */ +} txtdt_t; + +/** + * Text Data Language Unit. (Incomplete) + */ +typedef struct { + uint16_t lang_code; + uint8_t zero_1; + uint8_t char_set; /* 0x00 reserved Unicode, 0x01 ISO 646, 0x10 JIS Roman & JIS Kanji, 0x11 ISO 8859-1, 0x12 Shift JIS Kanji */ + uint32_t txtdt_start_byte; /* prt, rel start of vmg_txtdt_mgi */ + txtdt_t *txtdt; +} txtdt_lu_t; +#define TXTDT_LU_SIZE 8U + +/** + * Text Data Manager Information. (Incomplete) + */ +typedef struct { + char disc_name[12]; + uint16_t zero_1; + uint16_t nr_of_language_units; + uint32_t last_byte; + txtdt_lu_t *lu; +} txtdt_mgi_t; +#define TXTDT_MGI_SIZE 20U + + +/** + * VTS + * + * Structures relating to the Video Title Set (VTS). + */ + +/** + * Video Title Set Information Management Table. + */ +typedef struct { + char vts_identifier[12]; + uint32_t vts_last_sector; + uint8_t zero_1[12]; + uint32_t vtsi_last_sector; + uint8_t zero_2; + uint8_t specification_version; + uint32_t vts_category; + uint16_t zero_3; + uint16_t zero_4; + uint8_t zero_5; + uint8_t zero_6[19]; + uint16_t zero_7; + uint8_t zero_8[32]; + uint64_t zero_9; + uint8_t zero_10[24]; + uint32_t vtsi_last_byte; + uint32_t zero_11; + uint8_t zero_12[56]; + uint32_t vtsm_vobs; /* sector */ + uint32_t vtstt_vobs; /* sector */ + uint32_t vts_ptt_srpt; /* sector */ + uint32_t vts_pgcit; /* sector */ + uint32_t vtsm_pgci_ut; /* sector */ + uint32_t vts_tmapt; /* sector */ + uint32_t vtsm_c_adt; /* sector */ + uint32_t vtsm_vobu_admap; /* sector */ + uint32_t vts_c_adt; /* sector */ + uint32_t vts_vobu_admap; /* sector */ + uint8_t zero_13[24]; + + video_attr_t vtsm_video_attr; + uint8_t zero_14; + uint8_t nr_of_vtsm_audio_streams; /* should be 0 or 1 */ + audio_attr_t vtsm_audio_attr; + audio_attr_t zero_15[7]; + uint8_t zero_16[17]; + uint8_t nr_of_vtsm_subp_streams; /* should be 0 or 1 */ + subp_attr_t vtsm_subp_attr; + subp_attr_t zero_17[27]; + uint8_t zero_18[2]; + + video_attr_t vts_video_attr; + uint8_t zero_19; + uint8_t nr_of_vts_audio_streams; + audio_attr_t vts_audio_attr[8]; + uint8_t zero_20[17]; + uint8_t nr_of_vts_subp_streams; + subp_attr_t vts_subp_attr[32]; + uint16_t zero_21; + multichannel_ext_t vts_mu_audio_attr[8]; + /* XXX: how much 'padding' here, if any? */ +} vtsi_mat_t; + +/** + * PartOfTitle Unit Information. + */ +typedef struct { + uint16_t pgcn; + uint16_t pgn; +} ptt_info_t; + +/** + * PartOfTitle Information. + */ +typedef struct { + uint16_t nr_of_ptts; + ptt_info_t *ptt; +} ttu_t; + +/** + * PartOfTitle Search Pointer Table. + */ +typedef struct { + uint16_t nr_of_srpts; + uint16_t zero_1; + uint32_t last_byte; + ttu_t *title; + uint32_t *ttu_offset; /* offset table for each ttu */ +} vts_ptt_srpt_t; +#define VTS_PTT_SRPT_SIZE 8U + + +/** + * Time Map Entry. + */ +/* Should this be bit field at all or just the uint32_t? */ +typedef uint32_t map_ent_t; + +/** + * Time Map. + */ +typedef struct { + uint8_t tmu; /* Time unit, in seconds */ + uint8_t zero_1; + uint16_t nr_of_entries; + map_ent_t *map_ent; +} vts_tmap_t; +#define VTS_TMAP_SIZE 4U + +/** + * Time Map Table. + */ +typedef struct { + uint16_t nr_of_tmaps; + uint16_t zero_1; + uint32_t last_byte; + vts_tmap_t *tmap; + uint32_t *tmap_offset; /* offset table for each tmap */ +} vts_tmapt_t; +#define VTS_TMAPT_SIZE 8U + +/** + * The following structure defines an IFO file. The structure is divided into + * two parts, the VMGI, or Video Manager Information, which is read from the + * VIDEO_TS.[IFO,BUP] file, and the VTSI, or Video Title Set Information, which + * is read in from the VTS_XX_0.[IFO,BUP] files. + */ +typedef struct { + /* VMGI */ + vmgi_mat_t *vmgi_mat; + tt_srpt_t *tt_srpt; + pgc_t *first_play_pgc; + ptl_mait_t *ptl_mait; + vts_atrt_t *vts_atrt; + txtdt_mgi_t *txtdt_mgi; + + /* Common */ + pgci_ut_t *pgci_ut; + c_adt_t *menu_c_adt; + vobu_admap_t *menu_vobu_admap; + + /* VTSI */ + vtsi_mat_t *vtsi_mat; + vts_ptt_srpt_t *vts_ptt_srpt; + pgcit_t *vts_pgcit; + vts_tmapt_t *vts_tmapt; + c_adt_t *vts_c_adt; + vobu_admap_t *vts_vobu_admap; +} ifo_handle_t; diff --git a/vob_demux.py b/vob_demux.py index b3d0d33..9e1a6ca 100644 --- a/vob_demux.py +++ b/vob_demux.py @@ -1,87 +1,107 @@ -import sys -import os -import json -import subprocess as SP - - -def get_streams(path): - proc = SP.Popen( - [ - "ffprobe", - "-probesize", - str(0x7FFFFFFF), - "-analyzeduration", - str(0x7FFFFFFF), - "-v", - "fatal", - "-i", - path, - "-show_streams", - "-show_format", - "-print_format", - "json", - ], - stdout=SP.PIPE, - stdin=SP.DEVNULL, - bufsize=0, - ) - data = json.load(proc.stdout) - ret = proc.wait() - if ret != 0: - return [], {} - return data["streams"], data["format"] - - -types = { - "mpeg2video": "m2v", - "ac3": "ac3", - "dvd_subtitle": "sup", -} - - -def demux(path): - folder = os.path.dirname(path) - basename = os.path.splitext(os.path.basename(path))[0] - streams, fmt = get_streams(path) - cmd = [ - "ffmpeg", - "-y", - # "-fflags","+genpts+igndts", - "-probesize", - str(0x7FFFFFFF), - "-analyzeduration", - str(0x7FFFFFFF), - "-i", - path, - "-strict", - "-2", - "-vcodec", - "copy", - "-acodec", - "copy", - "-scodec", - "copy", - ] - need_ffmpeg = False - for stream in streams: - codec = stream["codec_name"] - ext = types.get(codec, codec) - idx = stream["index"] - hex_id = stream["id"] - codec_name = stream["codec_long_name"] - outfile = os.path.join(folder, f"{basename}_{idx}_{hex_id}") - if codec=="dvd_nav_packet": - continue - print(idx, hex_id, codec_name, codec) - if codec == "dvd_subtitle": - SP.check_call([ - "mencoder",path,"-vobsuboutindex",str(idx),"-vobsubout", outfile,"-nosound","-ovc", "copy", "-o",os.devnull - ]) - continue - cmd += ["-map", f"0:#{hex_id}", "-strict", "-2", outfile + f".{ext}"] - need_ffmpeg = True - if need_ffmpeg: - SP.check_call(cmd) - -if __name__=="__main__": - demux(sys.argv[1]) \ No newline at end of file +import json +import os +import subprocess as SP +import sys +import shutil + + +def get_streams(path): + proc = SP.Popen( + [ + "ffprobe", + "-probesize", + str(0x7FFFFFFF), + "-analyzeduration", + str(0x7FFFFFFF), + "-v", + "fatal", + "-i", + path, + "-show_streams", + "-show_format", + "-print_format", + "json", + ], + stdout=SP.PIPE, + stdin=SP.DEVNULL, + bufsize=0, + ) + data = json.load(proc.stdout) + ret = proc.wait() + if ret != 0: + return [], {} + return data["streams"], data["format"] + +def ccextract(files): + ccextractor = shutil.which("ccextractor") or shutil.which("ccextractorwinfull") + if ccextractor is None and os.name=="nt": + ccextractor=os.path.expandvars(os.path.join("${PROGRAMFILES(X86)}","CCExtractor","ccextractorwinfull.exe")) + if not os.path.isfile(ccextractor): + print("WARNING: CCExtractor not found") + return [] + new_files=[] + for file in files: + outfile=os.path.splitext(file)[0] + outfile=os.path.extsep.join([outfile, "cc.srt"]) + ret=SP.call([ccextractor, "-sc", "-sbs", "-autodash", "-trim","-nobom","-o", outfile, file]) + if ret==10: + if os.path.isfile(outfile): + os.unlink(outfile) + continue + new_files.append(outfile) + return new_files + + +types = {"mpeg2video": "m2v", "ac3": "ac3", "dvd_subtitle": "sub.mkv", "eia_608": "srt"} + +def demux(path): + folder = os.path.dirname(path) + basename = os.path.splitext(os.path.basename(path))[0] + streams, _ = get_streams(path) + cmd = [ + "ffmpeg", + "-y", + "-probesize", + str(0x7FFFFFFF), + "-analyzeduration", + str(0x7FFFFFFF), + "-i", + path, + "-strict", + "-2", + ] + caption_files = [] + for stream in streams: + codec = stream["codec_name"] + ext = types.get(codec, codec) + hex_id = stream["id"] + codec_name = stream["codec_long_name"] + outfile = os.path.join(folder, f"{basename}_{hex_id}") + if codec == "dvd_nav_packet": + continue + outfile = os.path.extsep.join([outfile, ext]) + print(hex_id, codec_name, codec) + if codec == "mpeg2video": + caption_files.append(outfile) + cmd += [ + "-map", + f"0:#{hex_id}", + "-vcodec", + "copy", + "-acodec", + "copy", + "-scodec", + "copy", + "-strict", + "-2", + outfile, + ] + SP.check_call(cmd) + ccextract(caption_files) + for file in os.listdir(folder): + if os.path.isfile(file): + if os.stat(file).st_size==0: + os.unlink(file) + +if __name__ == "__main__": + demux(sys.argv[1])