Pushed latest changes, add Telecine/Interlacing detection to ff_d2v.py

This commit is contained in:
Daniel S. 2022-01-07 18:27:31 +01:00
parent e06f1dfad5
commit 5e4d9d6965
11 changed files with 2403 additions and 2039 deletions

10
.gitignore vendored
View file

@ -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/

View file

@ -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`
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)

View file

@ -1,344 +1,344 @@
/*
* Copyright (C) 2001, 2002 Billy Biggs <vektor@dumbterm.net>,
* Håkan Hjort <d95hjort@dtek.chalmers.se>,
* Björn Englund <d4bjorn@dtek.chalmers.se>
*
* 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 <vektor@dumbterm.net>,
* Håkan Hjort <d95hjort@dtek.chalmers.se>,
* Björn Englund <d4bjorn@dtek.chalmers.se>
*
* 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 );

View file

@ -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)

View file

@ -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);

585
dvdnav.py
View file

@ -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 "<DVD Path={0.path} Title={0.title} Serial={0.serial}".format(self)
def get_blocks(self, title, angle=1, slang=None):
self.__check_error(self.lib.dvdnav_set_PGC_positioning_flag(self.dvd, 1))
self.__check_error(self.lib.dvdnav_title_play(self.dvd, title))
curr_angle = self.ffi.new("int32_t*", 0)
num_angles = self.ffi.new("int32_t*", 0)
self.__check_error(
self.lib.dvdnav_get_angle_info(self.dvd, curr_angle, num_angles)
)
if angle != 0:
if angle < 1 or angle > 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 "<DVD Path={0.path} Title={0.title} Serial={0.serial}".format(self)
def get_blocks(self, title, angle=1, slang=None):
self.__check_error(self.lib.dvdnav_set_PGC_positioning_flag(self.dvd, 1))
self.__check_error(self.lib.dvdnav_title_play(self.dvd, title))
curr_angle = self.ffi.new("int32_t*", 0)
num_angles = self.ffi.new("int32_t*", 0)
self.__check_error(
self.lib.dvdnav_get_angle_info(self.dvd, curr_angle, num_angles)
)
if angle != 0:
if angle < 1 or angle > 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!")

View file

@ -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)

635
ff_d2v.py
View file

@ -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)

View file

@ -1,215 +1,215 @@
/*
* Copyright (C) 2000, 2001, 2002 Björn Englund <d4bjorn@dtek.chalmers.se>,
* Håkan Hjort <d95hjort@dtek.chalmers.se>
*
* 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 <d4bjorn@dtek.chalmers.se>,
* Håkan Hjort <d95hjort@dtek.chalmers.se>
*
* 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 *);

File diff suppressed because it is too large Load diff

View file

@ -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])
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])