Browse Source

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

main
Daniel S. 6 months ago
parent
commit
5e4d9d6965
  1. 10
      .gitignore
  2. 20
      README.md
  3. 688
      dvd_reader.h
  4. 164
      dvd_ripper.py
  5. 18
      dvdcss.h
  6. 585
      dvdnav.py
  7. 332
      dvdread.py
  8. 635
      ff_d2v.py
  9. 430
      ifo_read.h
  10. 1366
      ifo_types.h
  11. 194
      vob_demux.py

10
.gitignore vendored

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

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

688
dvd_reader.h

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

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

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

585
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 "<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