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 symbols
cython_debug/ 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 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) # Setup (Windows)
1. Clone the repo 1. Clone the repo
2. `pip install cffi tqdm` 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` 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>, * Copyright (C) 2001, 2002 Billy Biggs <vektor@dumbterm.net>,
* Håkan Hjort <d95hjort@dtek.chalmers.se>, * Håkan Hjort <d95hjort@dtek.chalmers.se>,
* Björn Englund <d4bjorn@dtek.chalmers.se> * Björn Englund <d4bjorn@dtek.chalmers.se>
* *
* This file is part of libdvdread. * This file is part of libdvdread.
* *
* libdvdread is free software; you can redistribute it and/or modify * libdvdread is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or * the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* libdvdread is distributed in the hope that it will be useful, * libdvdread is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License along * You should have received a copy of the GNU General Public License along
* with libdvdread; if not, write to the Free Software Foundation, Inc., * with libdvdread; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
/** /**
* The DVD access interface. * The DVD access interface.
* *
* This file contains the functions that form the interface to to * This file contains the functions that form the interface to to
* reading files located on a DVD. * reading files located on a DVD.
*/ */
/** /**
* The current version. * The current version.
*/ */
/** /**
* The length of one Logical Block of a DVD. * The length of one Logical Block of a DVD.
*/ */
#define DVD_VIDEO_LB_LEN 2048 #define DVD_VIDEO_LB_LEN 2048
/** /**
* Maximum length of filenames allowed in UDF. * Maximum length of filenames allowed in UDF.
*/ */
#define MAX_UDF_FILE_NAME_LEN 2048 #define MAX_UDF_FILE_NAME_LEN 2048
typedef long int off_t; typedef long int off_t;
/** /**
* Opaque type that is used as a handle for one instance of an opened DVD. * 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_s dvd_reader_t;
typedef struct dvd_reader_device_s dvd_reader_device_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 *. * Opaque type for a file read handle, much like a normal fd or FILE *.
*/ */
typedef struct dvd_file_s dvd_file_t; typedef struct dvd_file_s dvd_file_t;
struct dvd_reader_stream_cb struct dvd_reader_stream_cb
{ {
int ( *pf_seek ) ( void *p_stream, uint64_t i_pos); int ( *pf_seek ) ( void *p_stream, uint64_t i_pos);
int ( *pf_read ) ( void *p_stream, void* buffer, int i_read); int ( *pf_read ) ( void *p_stream, void* buffer, int i_read);
int ( *pf_readv ) ( void *p_stream, void *p_iovec, int i_blocks); int ( *pf_readv ) ( void *p_stream, void *p_iovec, int i_blocks);
}; };
typedef struct dvd_reader_stream_cb dvd_reader_stream_cb; typedef struct dvd_reader_stream_cb dvd_reader_stream_cb;
/** /**
* Custom logger callback for DVDOpen[Stream]2 * Custom logger callback for DVDOpen[Stream]2
* @param private Handle as provided in Open functions * @param private Handle as provided in Open functions
* @param level Log level * @param level Log level
* @param fmt Format string * @param fmt Format string
* @param args Arguments list * @param args Arguments list
* pf_log(priv, level, fmt, args); * pf_log(priv, level, fmt, args);
*/ */
typedef enum typedef enum
{ {
DVD_LOGGER_LEVEL_INFO, DVD_LOGGER_LEVEL_INFO,
DVD_LOGGER_LEVEL_ERROR, DVD_LOGGER_LEVEL_ERROR,
DVD_LOGGER_LEVEL_WARN, DVD_LOGGER_LEVEL_WARN,
DVD_LOGGER_LEVEL_DEBUG, DVD_LOGGER_LEVEL_DEBUG,
} dvd_logger_level_t; } dvd_logger_level_t;
typedef struct typedef struct
{ {
// void ( *pf_log ) ( void *, dvd_logger_level_t, const char *, va_list ); // void ( *pf_log ) ( void *, dvd_logger_level_t, const char *, va_list );
void *pf_log; void *pf_log;
} dvd_logger_cb; } dvd_logger_cb;
/** /**
* Public type that is used to provide statistics on a handle. * Public type that is used to provide statistics on a handle.
*/ */
typedef struct { typedef struct {
off_t size; /**< Total size of file in bytes */ off_t size; /**< Total size of file in bytes */
int nr_parts; /**< Number of file parts */ int nr_parts; /**< Number of file parts */
off_t parts_size[9]; /**< Size of each part in bytes */ off_t parts_size[9]; /**< Size of each part in bytes */
} dvd_stat_t; } dvd_stat_t;
/** /**
* Opens a block device of a DVD-ROM file, or an image file, or a directory * 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. * name for a mounted DVD or HD copy of a DVD.
* The second form of Open function (DVDOpenStream) can be used to * The second form of Open function (DVDOpenStream) can be used to
* provide custom stream_cb functions to access the DVD (see libdvdcss). * 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 * 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. * device, then that device is used for CSS authentication using libdvdcss.
* If no device is available, then no CSS authentication is performed, * If no device is available, then no CSS authentication is performed,
* and we hope that the image is decrypted. * and we hope that the image is decrypted.
* *
* If the path given is a directory, then the files in that directory may be * If the path given is a directory, then the files in that directory may be
* in any one of these formats: * in any one of these formats:
* *
* path/VIDEO_TS/VTS_01_1.VOB * path/VIDEO_TS/VTS_01_1.VOB
* path/video_ts/vts_01_1.vob * path/video_ts/vts_01_1.vob
* path/VTS_01_1.VOB * path/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 path Specifies the the device, file or directory to be used.
* @param stream is a private handle used by stream_cb * @param stream is a private handle used by stream_cb
* @param stream_cb is a struct containing seek and read functions * @param stream_cb is a struct containing seek and read functions
* @return If successful a a read handle is returned. Otherwise 0 is returned. * @return If successful a a read handle is returned. Otherwise 0 is returned.
* *
* dvd = DVDOpen(path); * dvd = DVDOpen(path);
* dvd = DVDOpenStream(stream, &stream_cb); * dvd = DVDOpenStream(stream, &stream_cb);
*/ */
dvd_reader_t *DVDOpen( const char * ); dvd_reader_t *DVDOpen( const char * );
dvd_reader_t *DVDOpenStream( void *, dvd_reader_stream_cb * ); dvd_reader_t *DVDOpenStream( void *, dvd_reader_stream_cb * );
/** /**
* Same as DVDOpen, but with private handle to be passed back on callbacks * 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 path Specifies the the device, file or directory to be used.
* @param priv is a private handle * @param priv is a private handle
* @param logcb is a custom logger callback struct, or NULL if none needed * @param logcb is a custom logger callback struct, or NULL if none needed
* @param stream_cb is a struct containing seek and read functions * @param stream_cb is a struct containing seek and read functions
* @return If successful a a read handle is returned. Otherwise 0 is returned. * @return If successful a a read handle is returned. Otherwise 0 is returned.
* *
* dvd = DVDOpen2(priv, logcb, path); * dvd = DVDOpen2(priv, logcb, path);
* dvd = DVDOpenStream2(priv, logcb, &stream_cb); * dvd = DVDOpenStream2(priv, logcb, &stream_cb);
*/ */
dvd_reader_t *DVDOpen2( void *, const dvd_logger_cb *, const char * ); dvd_reader_t *DVDOpen2( void *, const dvd_logger_cb *, const char * );
dvd_reader_t *DVDOpenStream2( void *, const dvd_logger_cb *, dvd_reader_stream_cb * ); dvd_reader_t *DVDOpenStream2( void *, const dvd_logger_cb *, dvd_reader_stream_cb * );
/** /**
* Closes and cleans up the DVD reader object. * Closes and cleans up the DVD reader object.
* *
* You must close all open files before calling this function. * You must close all open files before calling this function.
* *
* @param dvd A read handle that should be closed. * @param dvd A read handle that should be closed.
* *
* DVDClose(dvd); * DVDClose(dvd);
*/ */
void DVDClose( dvd_reader_t * ); void DVDClose( dvd_reader_t * );
/** /**
* *
*/ */
typedef enum { typedef enum {
DVD_READ_INFO_FILE, /**< VIDEO_TS.IFO or VTS_XX_0.IFO (title) */ 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_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_MENU_VOBS, /**< VIDEO_TS.VOB or VTS_XX_0.VOB (title) */
DVD_READ_TITLE_VOBS /**< VTS_XX_[1-9].VOB (title). All files in DVD_READ_TITLE_VOBS /**< VTS_XX_[1-9].VOB (title). All files in
the title set are opened and read as a the title set are opened and read as a
single file. */ single file. */
} dvd_read_domain_t; } dvd_read_domain_t;
/** /**
* Stats a file on the DVD given the title number and domain. * Stats a file on the DVD given the title number and domain.
* The information about the file is stored in a dvd_stat_t * The information about the file is stored in a dvd_stat_t
* which contains information about the size of the file and * which contains information about the size of the file and
* the number of parts in case of a multipart file and the respective * the number of parts in case of a multipart file and the respective
* sizes of the parts. * sizes of the parts.
* A multipart file is for instance VTS_02_1.VOB, VTS_02_2.VOB, VTS_02_3.VOB * 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], * The size of VTS_02_1.VOB will be stored in stat->parts_size[0],
* VTS_02_2.VOB in stat->parts_size[1], ... * VTS_02_2.VOB in stat->parts_size[1], ...
* The total size (sum of all parts) is stored in stat->size and * The total size (sum of all parts) is stored in stat->size and
* stat->nr_parts will hold the number of parts. * stat->nr_parts will hold the number of parts.
* Only DVD_READ_TITLE_VOBS (VTS_??_[1-9].VOB) can be multipart files. * 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 * 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 * in the filesystem. These sizes are not needed to use any other
* functions in libdvdread. * functions in libdvdread.
* *
* @param dvd A dvd read handle. * @param dvd A dvd read handle.
* @param titlenum Which Video Title Set should be used, VIDEO_TS is 0. * @param titlenum Which Video Title Set should be used, VIDEO_TS is 0.
* @param domain Which domain. * @param domain Which domain.
* @param stat Pointer to where the result is stored. * @param stat Pointer to where the result is stored.
* @return If successful 0, otherwise -1. * @return If successful 0, otherwise -1.
* *
* int DVDFileStat(dvd, titlenum, domain, stat); * int DVDFileStat(dvd, titlenum, domain, stat);
*/ */
int DVDFileStat(dvd_reader_t *, int, dvd_read_domain_t, dvd_stat_t *); 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. * Opens a file on the DVD given the title number and domain.
* *
* If the title number is 0, the video manager information is opened * If the title number is 0, the video manager information is opened
* (VIDEO_TS.[IFO,BUP,VOB]). Returns a file structure which may be * (VIDEO_TS.[IFO,BUP,VOB]). Returns a file structure which may be
* used for reads, or 0 if the file was not found. * used for reads, or 0 if the file was not found.
* *
* @param dvd A dvd read handle. * @param dvd A dvd read handle.
* @param titlenum Which Video Title Set should be used, VIDEO_TS is 0. * @param titlenum Which Video Title Set should be used, VIDEO_TS is 0.
* @param domain Which domain. * @param domain Which domain.
* @return If successful a a file read handle is returned, otherwise 0. * @return If successful a a file read handle is returned, otherwise 0.
* *
* dvd_file = DVDOpenFile(dvd, titlenum, domain); */ * dvd_file = DVDOpenFile(dvd, titlenum, domain); */
dvd_file_t *DVDOpenFile( dvd_reader_t *, int, dvd_read_domain_t ); dvd_file_t *DVDOpenFile( dvd_reader_t *, int, dvd_read_domain_t );
/** /**
* Closes a file and frees the associated structure. * Closes a file and frees the associated structure.
* *
* @param dvd_file The file read handle to be closed. * @param dvd_file The file read handle to be closed.
* *
* DVDCloseFile(dvd_file); * DVDCloseFile(dvd_file);
*/ */
void DVDCloseFile( dvd_file_t * ); void DVDCloseFile( dvd_file_t * );
/** /**
* Reads block_count number of blocks from the file at the given block offset. * 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 * 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. * 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 * When reading from an encrypted drive, blocks are decrypted using libdvdcss
* where required. * where required.
* *
* @param dvd_file A file read handle. * @param dvd_file A file read handle.
* @param offset Block offset from the start of the file to start reading at. * @param offset Block offset from the start of the file to start reading at.
* @param block_count Number of block to read. * @param block_count Number of block to read.
* @param data Pointer to a buffer to write the data into. * @param data Pointer to a buffer to write the data into.
* @return Returns number of blocks read on success, -1 on error. * @return Returns number of blocks read on success, -1 on error.
* *
* blocks_read = DVDReadBlocks(dvd_file, offset, block_count, data); * blocks_read = DVDReadBlocks(dvd_file, offset, block_count, data);
*/ */
ssize_t DVDReadBlocks( dvd_file_t *, int, size_t, unsigned char * ); ssize_t DVDReadBlocks( dvd_file_t *, int, size_t, unsigned char * );
/** /**
* Seek to the given position in the file. Returns the resulting position in * 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 * 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 * byte reads from the file, the block read call always reads from the given
* offset. * offset.
* *
* @param dvd_file A file read handle. * @param dvd_file A file read handle.
* @param seek_offset Byte offset from the start of the file to seek to. * @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. * @return The resulting position in bytes from the beginning of the file.
* *
* offset_set = DVDFileSeek(dvd_file, seek_offset); * offset_set = DVDFileSeek(dvd_file, seek_offset);
*/ */
int32_t DVDFileSeek( dvd_file_t *, int32_t ); int32_t DVDFileSeek( dvd_file_t *, int32_t );
/** /**
* Reads the given number of bytes from the file. This call can only be used * 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 * 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. * reads from and increments the currrent seek position for the file.
* *
* @param dvd_file A file read handle. * @param dvd_file A file read handle.
* @param data Pointer to a buffer to write the data into. * @param data Pointer to a buffer to write the data into.
* @param bytes Number of bytes to read. * @param bytes Number of bytes to read.
* @return Returns number of bytes read on success, -1 on error. * @return Returns number of bytes read on success, -1 on error.
* *
* bytes_read = DVDReadBytes(dvd_file, data, bytes); * bytes_read = DVDReadBytes(dvd_file, data, bytes);
*/ */
ssize_t DVDReadBytes( dvd_file_t *, void *, size_t ); ssize_t DVDReadBytes( dvd_file_t *, void *, size_t );
/** /**
* Returns the file size in blocks. * Returns the file size in blocks.
* *
* @param dvd_file A file read handle. * @param dvd_file A file read handle.
* @return The size of the file in blocks, -1 on error. * @return The size of the file in blocks, -1 on error.
* *
* blocks = DVDFileSize(dvd_file); * blocks = DVDFileSize(dvd_file);
*/ */
ssize_t DVDFileSize( dvd_file_t * ); ssize_t DVDFileSize( dvd_file_t * );
/** /**
* Get a unique 128 bit disc ID. * Get a unique 128 bit disc ID.
* This is the MD5 sum of VIDEO_TS.IFO and the VTS_0?_0.IFO files * This is the MD5 sum of VIDEO_TS.IFO and the VTS_0?_0.IFO files
* in title order (those that exist). * in title order (those that exist).
* If you need a 'text' representation of the id, print it as a * If you need a 'text' representation of the id, print it as a
* hexadecimal number, using lowercase letters, discid[0] first. * hexadecimal number, using lowercase letters, discid[0] first.
* I.e. the same format as the command-line 'md5sum' program uses. * I.e. the same format as the command-line 'md5sum' program uses.
* *
* @param dvd A read handle to get the disc ID from * @param dvd A read handle to get the disc ID from
* @param discid The buffer to put the disc ID into. The buffer must * @param discid The buffer to put the disc ID into. The buffer must
* have room for 128 bits (16 chars). * have room for 128 bits (16 chars).
* @return 0 on success, -1 on error. * @return 0 on success, -1 on error.
*/ */
int DVDDiscID( dvd_reader_t *, unsigned char * ); int DVDDiscID( dvd_reader_t *, unsigned char * );
/** /**
* Get the UDF VolumeIdentifier and VolumeSetIdentifier * Get the UDF VolumeIdentifier and VolumeSetIdentifier
* from the PrimaryVolumeDescriptor. * from the PrimaryVolumeDescriptor.
* *
* @param dvd A read handle to get the disc ID from * @param dvd A read handle to get the disc ID from
* @param volid The buffer to put the VolumeIdentifier into. * @param volid The buffer to put the VolumeIdentifier into.
* The VolumeIdentifier is latin-1 encoded (8bit unicode) * The VolumeIdentifier is latin-1 encoded (8bit unicode)
* null terminated and max 32 bytes (including '\0') * null terminated and max 32 bytes (including '\0')
* @param volid_size No more than volid_size bytes will be copied to volid. * @param volid_size No more than volid_size bytes will be copied to volid.
* If the VolumeIdentifier is truncated because of this * If the VolumeIdentifier is truncated because of this
* it will still be null terminated. * it will still be null terminated.
* @param volsetid The buffer to put the VolumeSetIdentifier into. * @param volsetid The buffer to put the VolumeSetIdentifier into.
* The VolumeIdentifier is 128 bytes as * The VolumeIdentifier is 128 bytes as
* stored in the UDF PrimaryVolumeDescriptor. * stored in the UDF PrimaryVolumeDescriptor.
* Note that this is not a null terminated string. * Note that this is not a null terminated string.
* @param volsetid_size At most volsetid_size bytes will be copied to volsetid. * @param volsetid_size At most volsetid_size bytes will be copied to volsetid.
* @return 0 on success, -1 on error. * @return 0 on success, -1 on error.
*/ */
int DVDUDFVolumeInfo( dvd_reader_t *, char *, unsigned int, int DVDUDFVolumeInfo( dvd_reader_t *, char *, unsigned int,
unsigned char *, unsigned int ); unsigned char *, unsigned int );
int DVDFileSeekForce( dvd_file_t *, int offset, int force_size); int DVDFileSeekForce( dvd_file_t *, int offset, int force_size);
/** /**
* Get the ISO9660 VolumeIdentifier and VolumeSetIdentifier * Get the ISO9660 VolumeIdentifier and VolumeSetIdentifier
* *
* * Only use this function as fallback if DVDUDFVolumeInfo returns 0 * * * Only use this function as fallback if DVDUDFVolumeInfo returns 0 *
* * this will happen on a disc mastered only with a iso9660 filesystem * * * this will happen on a disc mastered only with a iso9660 filesystem *
* * All video DVD discs have UDF filesystem * * * All video DVD discs have UDF filesystem *
* *
* @param dvd A read handle to get the disc ID from * @param dvd A read handle to get the disc ID from
* @param volid The buffer to put the VolumeIdentifier into. * @param volid The buffer to put the VolumeIdentifier into.
* The VolumeIdentifier is coded with '0-9','A-Z','_' * The VolumeIdentifier is coded with '0-9','A-Z','_'
* null terminated and max 33 bytes (including '\0') * null terminated and max 33 bytes (including '\0')
* @param volid_size No more than volid_size bytes will be copied to volid. * @param volid_size No more than volid_size bytes will be copied to volid.
* If the VolumeIdentifier is truncated because of this * If the VolumeIdentifier is truncated because of this
* it will still be null terminated. * it will still be null terminated.
* @param volsetid The buffer to put the VolumeSetIdentifier into. * @param volsetid The buffer to put the VolumeSetIdentifier into.
* The VolumeIdentifier is 128 bytes as * The VolumeIdentifier is 128 bytes as
* stored in the ISO9660 PrimaryVolumeDescriptor. * stored in the ISO9660 PrimaryVolumeDescriptor.
* Note that this is not a null terminated string. * Note that this is not a null terminated string.
* @param volsetid_size At most volsetid_size bytes will be copied to volsetid. * @param volsetid_size At most volsetid_size bytes will be copied to volsetid.
* @return 0 on success, -1 on error. * @return 0 on success, -1 on error.
*/ */
int DVDISOVolumeInfo( dvd_reader_t *, char *, unsigned int, int DVDISOVolumeInfo( dvd_reader_t *, char *, unsigned int,
unsigned char *, unsigned int ); unsigned char *, unsigned int );
/** /**
* Sets the level of caching that is done when reading from a device * 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 dvd A read handle to get the disc ID from
* @param level The level of caching wanted. * @param level The level of caching wanted.
* -1 - returns the current setting. * -1 - returns the current setting.
* 0 - UDF Cache turned off. * 0 - UDF Cache turned off.
* 1 - (default level) Pointers to IFO files and some data from * 1 - (default level) Pointers to IFO files and some data from
* PrimaryVolumeDescriptor are cached. * PrimaryVolumeDescriptor are cached.
* *
* @return The level of caching. * @return The level of caching.
*/ */
int DVDUDFCacheLevel( dvd_reader_t *, int ); int DVDUDFCacheLevel( dvd_reader_t *, int );

View File

@ -1,65 +1,99 @@
import cffi import itertools as ITT
import os import json
import sys import os
import time import subprocess as SP
from dvdnav import DVDNav,DVDError import sys
from dvdread import DVDRead import time
import subprocess as SP from glob import glob
import json
from glob import glob import cffi
import itertools as ITT from datetime import timedelta
from vob_demux import demux from dvdnav import DVDError, DVDNav
from ff_d2v import make_d2v from dvdread import DVDRead
from ff_d2v import make_d2v, make_meta
def loadlib(dll_path, *includes, **kwargs): from vob_demux import demux
ffi = cffi.FFI()
for include in includes: def close_file_del_if_empty(fh):
ffi.cdef(open(include).read(), kwargs) if not fh:
return ffi, ffi.dlopen(dll_path) return False
if fh.tell() == 0:
for dvd_path in ITT.chain.from_iterable(map(glob,sys.argv[1:])): fh.close()
r = DVDRead(dvd_path) os.unlink(fh.name)
# r.grab_ifos() return False
# r.grab_vobs() else:
# exit() fh.close()
return True
out_folder = os.path.join(
"out", "_".join([r.disc_id, r.udf_disc_name or r.iso_disc_name]).replace(" ", "_")
) dur_thr = 60.0
os.makedirs(out_folder, exist_ok=True)
d = DVDNav(dvd_path) def process_m2v_files(path):
to_demux = [] for file in glob(os.path.join(path,"**", "*.m2v")):
for k, v in d.titles.items(): make_meta(file)
v["duration"] = v["duration"].total_seconds() make_d2v(file)
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: for dvd_path in ITT.chain.from_iterable(map(glob, sys.argv[1:])):
json.dump(v, fh) r = DVDRead(dvd_path)
for a in range(0,99): # r.grab_ifos()
block=0 # r.grab_vobs()
outfile = os.path.join(out_folder, f"t{k:03}_a{a:03}_b{block:03}.vob") # exit()
to_demux.append(outfile) if os.path.isfile(dvd_path):
fh = open(outfile, "wb") basename = os.path.splitext(os.path.basename(dvd_path))[0]
try: else:
for block in d.get_blocks(k, a): basename = r.iso_disc_name or r.udf_disc_name
if isinstance(block, int): base_dir = os.path.join("out", "_".join([basename, r.disc_id]).replace(" ", "_"))
outfile = os.path.join(out_folder, f"t{k:03}_a{a:03}_b{block:03}.vob") if os.path.isdir(base_dir):
to_demux.append(outfile) print(f"Output foldrer {base_dir} exists, remove to re-rip DVD")
if fh: process_m2v_files(base_dir)
fh.close() continue
fh = open(outfile, "wb") os.makedirs(base_dir, exist_ok=True)
else: d = DVDNav(dvd_path)
fh.write(block) to_demux = []
except DVDError as e: for k, v in d.titles.items():
if str(e)!="Invalid angle specified!": out_folder=os.path.join(base_dir,f"t{k:03}")
raise v["duration"] = v["duration"].total_seconds()
if fh.tell()==0: if v["chapters"]:
fh.close() v["chapters"] = [0.0]+[c.total_seconds() for c in v["chapters"]]
os.unlink(fh.name) avg_chapter_len = v["duration"] / len(v["chapters"])
while fh.name in to_demux: # if avg_chapter_len<10:
to_demux.remove(fh.name) # continue
for file in to_demux: d.titles[k] = v
demux(file) # if not v.get('audio'):
os.unlink(file) # print(f"[{k}|0] Skipping title {k} because it has no audio tracks")
for file in glob(os.path.join(out_folder,"*.m2v")): # continue
make_d2v(file) # 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_s * dvdcss_t;
typedef struct dvdcss_stream_cb dvdcss_stream_cb; typedef struct dvdcss_stream_cb dvdcss_stream_cb;
dvdcss_t dvdcss_open (const char *psz_target); dvdcss_t dvdcss_open (const char *psz_target);
dvdcss_t dvdcss_open_stream (void *p_stream, dvdcss_stream_cb *p_stream_cb); dvdcss_t dvdcss_open_stream (void *p_stream, dvdcss_stream_cb *p_stream_cb);
int dvdcss_close (dvdcss_t); int dvdcss_close (dvdcss_t);
int dvdcss_seek (dvdcss_t, int i_blocks, int i_flags); 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_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); int dvdcss_readv(dvdcss_t, void *p_iovec, int i_blocks, int i_flags);
const char* dvdcss_error (const dvdcss_t); const char* dvdcss_error (const dvdcss_t);
int dvdcss_is_scrambled (dvdcss_t); int dvdcss_is_scrambled (dvdcss_t);

585
dvdnav.py
View File

@ -1,265 +1,320 @@
import cffi import functools
import os import os
import functools from datetime import timedelta
from datetime import timedelta
from tqdm import tqdm import cffi
from dvdread import DVDRead from tqdm import tqdm
from dvdread import DVDRead
def loadlib(dll_path, *includes, **kwargs):
ffi = cffi.FFI()
for include in includes: def loadlib(dll_path, *includes, **kwargs):
ffi.cdef(open(include).read(), kwargs) ffi = cffi.FFI()
return ffi, ffi.dlopen(dll_path) for include in includes:
ffi.cdef(open(include).read(), kwargs)
return ffi, ffi.dlopen(dll_path)
class DVDError(Exception):
pass
domains = {
0: "None",
class DVDNav(object): 1: "FirstPlay",
def __init__(self, path, verbose=None, method="disc"): 2: "VTSTitle",
if verbose is None: 4: "VMGM",
os.environ.pop("DVDCSS_VERBOSE", None) 8: "VTSMenu",
else: }
os.environ["DVDCSS_VERBOSE"] = str(verbose) events = {
os.environ["DVDCSS_METHOD"] = method 0: "DVDNAV_BLOCK_OK",
self.dvd = None 1: "DVDNAV_NOP",
self.ffi, self.lib = loadlib( 2: "DVDNAV_STILL_FRAME",
"libdvdnav-4.dll", 3: "DVDNAV_SPU_STREAM_CHANGE",
"dvd_types.h", 4: "DVDNAV_AUDIO_STREAM_CHANGE",
"dvd_reader.h", 5: "DVDNAV_VTS_CHANGE",
"ifo_types.h", 6: "DVDNAV_CELL_CHANGE",
"nav_types.h", 7: "DVDNAV_NAV_PACKET",
"dvdnav_events.h", 8: "DVDNAV_STOP",
"dvdnav.h", 9: "DVDNAV_HIGHLIGHT",
pack=True, 10: "DVDNAV_SPU_CLUT_CHANGE",
) 12: "DVDNAV_HOP_CHANNEL",
self.path = path 13: "DVDNAV_WAIT",
self.titles = {} }
self.open(path)
class DVDError(Exception):
def __del__(self): pass
self.__check_error(self.lib.dvdnav_close(self.dvd))
self.dvd = None
class DVDNav(object):
def __repr__(self): def __init__(self, path, verbose=None, method="disc"):
return "<DVD Path={0.path} Title={0.title} Serial={0.serial}".format(self) if verbose is None:
os.environ.pop("DVDCSS_VERBOSE", None)
def get_blocks(self, title, angle=1, slang=None): else:
self.__check_error(self.lib.dvdnav_set_PGC_positioning_flag(self.dvd, 1)) os.environ["DVDCSS_VERBOSE"] = str(verbose)
self.__check_error(self.lib.dvdnav_title_play(self.dvd, title)) os.environ["DVDCSS_METHOD"] = method
curr_angle = self.ffi.new("int32_t*", 0) self.dvd = None
num_angles = self.ffi.new("int32_t*", 0) self.ffi, self.lib = loadlib(
self.__check_error( "libdvdnav-4.dll",
self.lib.dvdnav_get_angle_info(self.dvd, curr_angle, num_angles) "dvd_types.h",
) "dvd_reader.h",
if angle != 0: "ifo_types.h",
if angle < 1 or angle > num_angles[0]: "nav_types.h",
raise DVDError("Invalid angle specified!") "dvdnav_events.h",
if angle != curr_angle[0]: "dvdnav.h",
self.__check_error(self.lib.dvdnav_angle_change(self.dvd, angle)) pack=True,
if slang is not None: )
self.__check_error(self.lib.dvdnav_spu_language_select(self.dvd, slang)) self.path = path
event = self.lib.DVDNAV_NOP self.titles = {}
buf = self.ffi.new("char[]", 4096) self.open(path)
ev = self.ffi.new("int32_t*", self.lib.DVDNAV_NOP)
size = self.ffi.new("int32_t*", 0) def __del__(self):
pos = self.ffi.new("uint32_t*", 0) self.__check_error(self.lib.dvdnav_close(self.dvd))
total_size = self.ffi.new("uint32_t*", 0) self.dvd = None
domains = {
1: "FirstPlay", def __repr__(self):
2: "VTSTitle", return "<DVD Path={0.path} Title={0.title} Serial={0.serial}".format(self)
4: "VMGM",
8: "VTSMenu", def get_blocks(self, title, angle=1, slang=None):
} self.__check_error(self.lib.dvdnav_set_PGC_positioning_flag(self.dvd, 1))
events = { self.__check_error(self.lib.dvdnav_title_play(self.dvd, title))
0: "DVDNAV_BLOCK_OK", curr_angle = self.ffi.new("int32_t*", 0)
1: "DVDNAV_NOP", num_angles = self.ffi.new("int32_t*", 0)
2: "DVDNAV_STILL_FRAME", self.__check_error(
3: "DVDNAV_SPU_STREAM_CHANGE", self.lib.dvdnav_get_angle_info(self.dvd, curr_angle, num_angles)
4: "DVDNAV_AUDIO_STREAM_CHANGE", )
5: "DVDNAV_VTS_CHANGE", if angle != 0:
6: "DVDNAV_CELL_CHANGE", if angle < 1 or angle > num_angles[0]:
7: "DVDNAV_NAV_PACKET", raise DVDError("Invalid angle specified!")
8: "DVDNAV_STOP", if angle != curr_angle[0]:
9: "DVDNAV_HIGHLIGHT", self.__check_error(self.lib.dvdnav_angle_change(self.dvd, angle))
10: "DVDNAV_SPU_CLUT_CHANGE", if slang is not None:
12: "DVDNAV_HOP_CHANNEL", self.__check_error(self.lib.dvdnav_spu_language_select(self.dvd, slang))
13: "DVDNAV_WAIT", event = self.lib.DVDNAV_NOP
} buf = self.ffi.new("char[]", 4096)
progbar = tqdm( ev = self.ffi.new("int32_t*", self.lib.DVDNAV_NOP)
unit_divisor=1024, size = self.ffi.new("int32_t*", 0)
unit_scale=True, pos = self.ffi.new("uint32_t*", 0)
unit="iB", total_size = self.ffi.new("uint32_t*", 0)
desc="Ripping DVD", progbar = tqdm(
disable=False, unit_divisor=1024,
) unit_scale=True,
ripped = set() unit="iB",
current_vts = None desc="Ripping DVD",
current_cell = None disable=False,
current_pg = None )
while True: ripped = set()
self.__check_error(self.lib.dvdnav_get_next_block(self.dvd, buf, ev, size)) cells = set()
if ( current_vts = (None,None)
self.lib.dvdnav_get_position(self.dvd, pos, total_size) current_cell = None
== self.lib.DVDNAV_STATUS_OK current_pg = None
): while True:
progbar.total = total_size[0] * 2048 self.__check_error(self.lib.dvdnav_get_next_block(self.dvd, buf, ev, size))
progbar.n = max(progbar.n, min(progbar.total, pos[0] * 2048)) if (
progbar.update(0) self.lib.dvdnav_get_position(self.dvd, pos, total_size)
progbar.set_postfix( == self.lib.DVDNAV_STATUS_OK
vts=current_vts, ):
cell=current_cell, progbar.total = total_size[0] * 2048
pg=current_pg, progbar.n = max(progbar.n, min(progbar.total, pos[0] * 2048))
angle=angle, progbar.update(0)
title=title, progbar.set_postfix(
) vts=current_vts,
# print("Got event:",events.get(ev[0],ev[0]),size[0]) cell=current_cell,
if ev[0] in [ pg=current_pg,
self.lib.DVDNAV_SPU_CLUT_CHANGE, angle=angle,
self.lib.DVDNAV_HOP_CHANNEL, title=title,
self.lib.DVDNAV_NOP, )
self.lib.DVDNAV_HIGHLIGHT, # print("Got event:",events.get(ev[0],ev[0]),size[0])
]: if ev[0] in [
continue self.lib.DVDNAV_SPU_CLUT_CHANGE,
elif ev[0] == self.lib.DVDNAV_BLOCK_OK: self.lib.DVDNAV_HOP_CHANNEL,
yield self.ffi.buffer(buf, size[0])[:] self.lib.DVDNAV_NOP,
elif ev[0] == self.lib.DVDNAV_STOP: self.lib.DVDNAV_HIGHLIGHT,
progbar.write(f"[{title}|{angle}] Stop") ]:
break continue
elif ev[0] == self.lib.DVDNAV_NAV_PACKET: elif ev[0] == self.lib.DVDNAV_BLOCK_OK:
pass yield self.ffi.buffer(buf, size[0])[:]
elif ev[0] == self.lib.DVDNAV_STILL_FRAME: elif ev[0] == self.lib.DVDNAV_STOP:
self.__check_error(self.lib.dvdnav_still_skip(self.dvd)) progbar.write(f"[{title}|{angle}] Stop")
elif ev[0] == self.lib.DVDNAV_WAIT: break
self.__check_error(self.lib.dvdnav_wait_skip(self.dvd)) elif ev[0] == self.lib.DVDNAV_NAV_PACKET:
elif ev[0] == self.lib.DVDNAV_SPU_STREAM_CHANGE: pass
pass elif ev[0] == self.lib.DVDNAV_STILL_FRAME:
elif ev[0] == self.lib.DVDNAV_AUDIO_STREAM_CHANGE: self.__check_error(self.lib.dvdnav_still_skip(self.dvd))
audio = self.ffi.cast("dvdnav_audio_stream_change_event_t*", buf) elif ev[0] == self.lib.DVDNAV_WAIT:
elif ev[0] == self.lib.DVDNAV_CELL_CHANGE: self.__check_error(self.lib.dvdnav_wait_skip(self.dvd))
cell = self.ffi.cast("dvdnav_cell_change_event_t*", buf) elif ev[0] == self.lib.DVDNAV_SPU_STREAM_CHANGE:
current_cell = cell.cellN pass
current_pg = cell.pgN elif ev[0] == self.lib.DVDNAV_AUDIO_STREAM_CHANGE:
progbar.write( audio = self.ffi.cast("dvdnav_audio_stream_change_event_t*", buf)
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_CELL_CHANGE:
) cell = self.ffi.cast("dvdnav_cell_change_event_t*", buf)
elif ev[0] == self.lib.DVDNAV_VTS_CHANGE: current_cell = cell.cellN
vts = self.ffi.cast("dvdnav_vts_change_event_t*", buf) current_pg = cell.pgN
new_vts = (vts.new_vtsN, vts.new_domain) progbar.write(
ripped.add((vts.old_vtsN, vts.old_domain)) 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)})"
# 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 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)
progbar.write(f"[{title}|{angle}] Looped!") if fp in cells:
break progbar.write(f"[{title}|{angle}] Cells Looped!")
current_vts = (vts.new_vtsN, vts.new_domain) break
if vts.new_domain == 8: # back to menu cells.add(fp)
progbar.write(f"[{title}|{angle}] Back to menu!") elif ev[0] == self.lib.DVDNAV_VTS_CHANGE:
break vts = self.ffi.cast("dvdnav_vts_change_event_t*", buf)
yield vts.new_vtsN old_domain = domains[vts.old_domain]
else: new_domain = domains[vts.new_domain]
progbar.write( new_vts = (vts.new_vtsN, vts.new_domain)
f"[{title}|{angle}] Unhandled: {events.get(ev[0],ev[0])} {size[0]}" old_vts = (vts.old_vtsN, vts.old_domain)
) ripped.add((vts.old_vtsN, vts.old_domain))
self.__check_error(self.lib.dvdnav_stop(self.dvd)) cells.clear()
progbar.write(f"[{title}|{angle}] VTS: {vts.old_vtsN} ({vts.old_domain} {old_domain}) -> {vts.new_vtsN} ({vts.new_domain} {new_domain})")
def __check_error(self, ret): if (new_vts in ripped) or new_vts==old_vts: # looped
if ret == self.lib.DVDNAV_STATUS_ERR: progbar.write(f"[{title}|{angle}] VTS Looped!")
if self.dvd: break
err = self.ffi.string(self.lib.dvdnav_err_to_string(self.dvd)) current_vts = (vts.new_vtsN, vts.new_domain)
raise DVDError(err) if vts.new_domain == 8: # back to menu
raise DVDError("Unknown error") progbar.write(f"[{title}|{angle}] VTS Back to menu!")
break
def __get_titles(self): # yield vts.new_vtsN
titles = self.ffi.new("int32_t*", 0) else:
p_times = self.ffi.new("uint64_t[]", 512) progbar.write(
times = self.ffi.new("uint64_t**", p_times) f"[{title}|{angle}] Unhandled: {events.get(ev[0],ev[0])} {size[0]}"
duration = self.ffi.new("uint64_t*", 0) )
titles = self.ffi.new("int32_t*", 0) self.__check_error(self.lib.dvdnav_stop(self.dvd))
self.lib.dvdnav_get_number_of_titles(self.dvd, titles) progbar.close()
num_titles = titles[0]
for title in range(0, num_titles + 1): def __check_error(self, ret):
if self.lib.dvdnav_get_number_of_parts(self.dvd, title, titles) == 0: if ret == self.lib.DVDNAV_STATUS_ERR:
continue if self.dvd:
num_parts = titles[0] err = self.ffi.string(self.lib.dvdnav_err_to_string(self.dvd))
self.lib.dvdnav_get_number_of_angles(self.dvd, title, titles) raise DVDError(err)
num_angles = titles[0] raise DVDError("Unknown error")
num_chapters = self.lib.dvdnav_describe_title_chapters(
self.dvd, title, times, duration def __get_vts(self,title):
) buf = self.ffi.new("char[]", 4096)
if duration[0] == 0: ev = self.ffi.new("int32_t*", self.lib.DVDNAV_NOP)
continue size = self.ffi.new("int32_t*", 0)
chapters = [] pos = self.ffi.new("uint32_t*", 0)
for t in range(num_chapters): total_size = self.ffi.new("uint32_t*", 0)
chapters.append(timedelta(seconds=times[0][t] / 90000)) self.__check_error(self.lib.dvdnav_set_PGC_positioning_flag(self.dvd, 1))
self.titles[title] = { self.__check_error(self.lib.dvdnav_title_play(self.dvd, title))
"parts": num_parts, seq=[]
"angles": num_angles, while True:
"duration": timedelta(seconds=duration[0] / 90000), self.__check_error(self.lib.dvdnav_get_next_block(self.dvd, buf, ev, size))
"chapters": chapters, 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])
def __get_info(self): if self.lib.dvdnav_next_pg_search(self.dvd)==0:
s = self.ffi.new("char**", self.ffi.NULL) break
self.lib.dvdnav_get_title_string(self.dvd, s) elif ev[0] == self.lib.DVDNAV_STOP:
self.title = str(self.ffi.string(s[0]), "utf8").strip() or None break
self.lib.dvdnav_get_serial_string(self.dvd, s) elif ev[0] == self.lib.DVDNAV_STILL_FRAME:
self.serial = str(self.ffi.string(s[0]), "utf8").strip() or None self.__check_error(self.lib.dvdnav_still_skip(self.dvd))
self.__get_titles() elif ev[0] == self.lib.DVDNAV_WAIT:
self.__check_error(self.lib.dvdnav_wait_skip(self.dvd))
def open(self, path): elif ev[0] == self.lib.DVDNAV_VTS_CHANGE:
audio_attrs = self.ffi.new("audio_attr_t*") vts = self.ffi.cast("dvdnav_vts_change_event_t*", buf)
spu_attr = self.ffi.new("subp_attr_t*") old_domain = domains[vts.old_domain]
dvdnav = self.ffi.new("dvdnav_t**", self.ffi.cast("dvdnav_t*", 0)) new_domain = domains[vts.new_domain]
self.__check_error(self.lib.dvdnav_open(dvdnav, bytes(path, "utf8"))) seq.append(
self.dvd = dvdnav[0] (vts.new_vtsN, new_domain)
self.__check_error(self.lib.dvdnav_set_readahead_flag(self.dvd, 1)) )
self.__get_info() if vts.new_domain==8:
for title in self.titles: break
self.__check_error(self.lib.dvdnav_title_play(self.dvd, title)) continue
self.titles[title]["audio"] = {} # print(title,ev[0],size[0])
self.titles[title]["subtitles"] = {} self.__check_error(self.lib.dvdnav_stop(self.dvd))
for n in range(255): # print(title,seq)
stream_id = self.lib.dvdnav_get_audio_logical_stream(self.dvd, n) return seq
if stream_id == -1: # self.__check_error(self.lib.dvdnav_next_pg_search(self.dvd))
continue
self.__check_error( def __get_titles(self):
self.lib.dvdnav_get_audio_attr(self.dvd, stream_id, audio_attrs) titles = self.ffi.new("int32_t*", 0)
) p_times = self.ffi.new("uint64_t[]", 512)
alang = None times = self.ffi.new("uint64_t**", p_times)
if audio_attrs.lang_type: duration = self.ffi.new("uint64_t*", 0)
alang = str(audio_attrs.lang_code.to_bytes(2, "big"), "utf8") titles = self.ffi.new("int32_t*", 0)
channels = audio_attrs.channels + 1 self.lib.dvdnav_get_number_of_titles(self.dvd, titles)
codec = {0: "ac3", 2: "mpeg1", 3: "mpeg-2ext", 4: "lpcm", 6: "dts"}[ num_titles = titles[0]
audio_attrs.audio_format for title in range(0, num_titles + 1):
] if self.lib.dvdnav_get_number_of_parts(self.dvd, title, titles) == 0:
audio_type = { continue
0: None, num_parts = titles[0]
1: "normal", self.lib.dvdnav_get_number_of_angles(self.dvd, title, titles)
2: "descriptive", num_angles = titles[0]
3: "director's commentary", num_chapters = self.lib.dvdnav_describe_title_chapters(
4: "alternate director's commentary", self.dvd, title, times, duration
}[audio_attrs.code_extension] )
self.titles[title]["audio"][n] = { if duration[0] == 0:
"stream_id": stream_id, continue
"lang": alang, chapters = []
"channels": channels, if num_chapters==0 and times[0]==self.ffi.NULL:
"codec": codec, chapters=None
"type": audio_type, for t in range(num_chapters):
} chapters.append(timedelta(seconds=times[0][t] / 90000))
for n in range(255): self.titles[title] = {
stream_id = self.lib.dvdnav_get_spu_logical_stream(self.dvd, n) "parts": num_parts,
if stream_id == -1: "angles": num_angles,
continue "duration": timedelta(seconds=duration[0] / 90000),
self.__check_error( "chapters": chapters,
self.lib.dvdnav_get_spu_attr(self.dvd, stream_id, spu_attr) }
)
slang = None def __get_info(self):
if spu_attr.type == 1: s = self.ffi.new("char**", self.ffi.NULL)
slang = str(spu_attr.lang_code.to_bytes(2, "big"), "utf8") self.lib.dvdnav_get_title_string(self.dvd, s)
self.titles[title]["subtitles"][n] = { self.title = str(self.ffi.string(s[0]), "utf8").strip() or None
"stream_id": stream_id, self.lib.dvdnav_get_serial_string(self.dvd, s)
"lang": slang, self.serial = str(self.ffi.string(s[0]), "utf8").strip() or None
} self.__get_titles()
self.__check_error(self.lib.dvdnav_stop(self.dvd))
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 binascii
import os import functools
import functools import os
import binascii from datetime import timedelta
from datetime import timedelta
import cffi
def loadlib(dll_path, *includes, **kwargs):
ffi = cffi.FFI() def loadlib(dll_path, *includes, **kwargs):
for include in includes: ffi = cffi.FFI()
ffi.cdef(open(include).read(), **kwargs) for include in includes:
return ffi, ffi.dlopen(dll_path) ffi.cdef(open(include).read(), **kwargs)
return ffi, ffi.dlopen(dll_path)
class DVDRead(object):
def __init__(self, path, verbose="0", method="disc"): class DVDRead(object):
if verbose is None: def __init__(self, path, verbose="0", method="disc"):
os.environ.pop("DVDCSS_VERBOSE", None) if verbose is None:
else: os.environ.pop("DVDCSS_VERBOSE", None)
os.environ["DVDCSS_VERBOSE"] = str(verbose) else:
os.environ["DVDCSS_METHOD"] = method os.environ["DVDCSS_VERBOSE"] = str(verbose)
self.dvd = None os.environ["DVDCSS_METHOD"] = method
self.ffi, self.lib = loadlib( self.dvd = None
"libdvdread-8.dll", self.ffi, self.lib = loadlib(
"dvd_types.h", "libdvdread-8.dll",
"dvd_reader.h", "dvd_types.h",
"ifo_types.h", "dvd_reader.h",
"ifo_read.h", "ifo_types.h",
"ifo_print.h", "ifo_read.h",
"nav_types.h", "ifo_print.h",
"nav_read.h", "nav_types.h",
"nav_print.h", "nav_read.h",
packed=True, "nav_print.h",
) packed=True,
self.path = path )
self.titles = {} self.path = path
self.open(path) self.titles = {}
self.lb_len=self.lib.DVD_VIDEO_LB_LEN self.open(path)
self.lb_len=self.lib.DVD_VIDEO_LB_LEN
def grab_ifo(self,title,bup=False):
from tqdm import tqdm def grab_ifo(self,title,bup=False):
buf = self.ffi.new("unsigned char[]", 512) from tqdm import tqdm
if bup: buf = self.ffi.new("unsigned char[]", 512)
fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_BACKUP_FILE) if bup:
else: fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_BACKUP_FILE)
fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_FILE) else:
total_size = self.lib.DVDFileSize(fh)*self.lb_len fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_FILE)
remaining = total_size total_size = self.lib.DVDFileSize(fh)*self.lb_len
num_read = True remaining = total_size
pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False) num_read = True
while num_read: pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False)
num_read=self.lib.DVDReadBytes( fh, buf, 512) while num_read:
num_read=min(num_read,remaining) num_read=self.lib.DVDReadBytes( fh, buf, 512)
remaining-=num_read num_read=min(num_read,remaining)
pbar.update(num_read) remaining-=num_read
yield self.ffi.buffer(buf,num_read)[:] pbar.update(num_read)
self.lib.DVDCloseFile(fh) yield self.ffi.buffer(buf,num_read)[:]
self.lib.DVDCloseFile(fh)
def grab_ifos(self): pbar.close()
vmg_ifo = self.lib.ifoOpen(self.dvd, 0)
if vmg_ifo == self.ffi.NULL: def grab_ifos(self):
return vmg_ifo = self.lib.ifoOpen(self.dvd, 0)
title_sets = vmg_ifo.vts_atrt.nr_of_vtss if vmg_ifo == self.ffi.NULL:
for t in range(1,title_sets + 1): return
vts = self.lib.ifoOpen(self.dvd, t) title_sets = vmg_ifo.vts_atrt.nr_of_vtss
if vts == self.ffi.NULL: for t in range(1,title_sets + 1):
continue vts = self.lib.ifoOpen(self.dvd, t)
self.lib.ifoClose(vts) if vts == self.ffi.NULL:
outfile=os.path.join("RIP",f"VTS_{t:02}_0.ifo") continue
with open(outfile, "wb") as out_ifo: self.lib.ifoClose(vts)
for block in self.grab_ifo(t,bup=False): outfile=os.path.join("RIP",f"VTS_{t:02}_0.ifo")
out_ifo.write(block) with open(outfile, "wb") as out_ifo:
outfile=os.path.join("RIP",f"VTS_{t:02}_0.bup") for block in self.grab_ifo(t,bup=False):
with open(outfile, "wb") as out_ifo: out_ifo.write(block)
for block in self.grab_ifo(t,bup=True): outfile=os.path.join("RIP",f"VTS_{t:02}_0.bup")
out_ifo.write(block) with open(outfile, "wb") as out_ifo:
self.lib.ifoClose(vmg_ifo) for block in self.grab_ifo(t,bup=True):
out_ifo.write(block)
def grab_vob(self,title): self.lib.ifoClose(vmg_ifo)
from tqdm import tqdm
buf = self.ffi.new("unsigned char[]", 512 * self.lb_len) def grab_vob(self,title):
fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_TITLE_VOBS) from tqdm import tqdm
total_size = self.lib.DVDFileSize(fh)*self.lb_len buf = self.ffi.new("unsigned char[]", 512 * self.lb_len)
remaining = total_size fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_TITLE_VOBS)
num_read = True total_size = self.lib.DVDFileSize(fh)*self.lb_len
pos=0 remaining = total_size
pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False) num_read = True
while remaining: pos=0
num_read=self.lib.DVDReadBlocks( fh,pos, 512, buf) pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False)
if num_read<0: while remaining:
raise RuntimeError("Error reading!") num_read=self.lib.DVDReadBlocks( fh,pos, 512, buf)
num_read_bytes=num_read*self.lb_len if num_read<0:
num_read_bytes=min(num_read_bytes,remaining) raise RuntimeError("Error reading!")
remaining-=num_read_bytes num_read_bytes=num_read*self.lb_len
pbar.update(num_read_bytes) num_read_bytes=min(num_read_bytes,remaining)
yield self.ffi.buffer(buf,num_read_bytes)[:] remaining-=num_read_bytes
pos+=num_read pbar.update(num_read_bytes)
pbar.close() yield self.ffi.buffer(buf,num_read_bytes)[:]
self.lib.DVDCloseFile(fh) pos+=num_read
pbar.close()
def grab_vobs(self): self.lib.DVDCloseFile(fh)
vmg_ifo = self.lib.ifoOpen(self.dvd, 0)
if vmg_ifo == self.ffi.NULL: def grab_vobs(self):
return vmg_ifo = self.lib.ifoOpen(self.dvd, 0)
title_sets = vmg_ifo.vts_atrt.nr_of_vtss if vmg_ifo == self.ffi.NULL:
for t in range(1,title_sets + 1): return
vts = self.lib.ifoOpen(self.dvd, t) title_sets = vmg_ifo.vts_atrt.nr_of_vtss
if vts == self.ffi.NULL: for t in range(1,title_sets + 1):
continue vts = self.lib.ifoOpen(self.dvd, t)
self.lib.ifoClose(vts) if vts == self.ffi.NULL:
outfile=os.path.join("RIP",f"VTS_{t:02}_0.vob") continue
with open(outfile, "wb") as out_ifo: self.lib.ifoClose(vts)
for block in self.grab_vob(t): outfile=os.path.join("RIP",f"VTS_{t:02}_0.vob")
out_ifo.write(block) with open(outfile, "wb") as out_ifo:
self.lib.ifoClose(vmg_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 def test(self):
chunk_size = 2048 from tqdm import tqdm
buf = self.ffi.new("unsigned char[]", chunk_size * 2048) fn = 0
for fn in range(title_sets + 1): chunk_size = 2048
pos = 0 buf = self.ffi.new("unsigned char[]", chunk_size * 2048)
fh = self.lib.DVDOpenFile(self.dvd, fn, self.lib.DVD_READ_TITLE_VOBS) for fn in range(title_sets + 1):
if fh: pos = 0
total_size = self.lib.DVDFileSize(fh) fh = self.lib.DVDOpenFile(self.dvd, fn, self.lib.DVD_READ_TITLE_VOBS)
if total_size == -1: if fh:
self.lib.DVDCloseFile(fh) total_size = self.lib.DVDFileSize(fh)
break if total_size == -1:
pbar = tqdm(total=total_size * 2048, unit="iB", unit_scale=True, unit_divisor=1024,leave=False) self.lib.DVDCloseFile(fh)
last=False break
with open(f"out_{fn}.vob", "wb") as out_vob: pbar = tqdm(total=total_size * 2048, unit="iB", unit_scale=True, unit_divisor=1024,leave=False)
while True: last=False
if (pos+chunk_size)>total_size: with open(f"out_{fn}.vob", "wb") as out_vob:
chunk_size=total_size-pos while True:
count = self.lib.DVDReadBlocks(fh, pos, chunk_size, buf) if (pos+chunk_size)>total_size:
if count == -1: chunk_size=total_size-pos
break count = self.lib.DVDReadBlocks(fh, pos, chunk_size, buf)
pbar.update( if count == -1:
out_vob.write(self.ffi.buffer(buf, count * 2048)[:]) break
) pbar.update(
pos += count out_vob.write(self.ffi.buffer(buf, count * 2048)[:])
if pos>=total_size: )
break pos += count
self.lib.DVDCloseFile(fh) if pos>=total_size:
fn += 1 break
if fn>200: self.lib.DVDCloseFile(fh)
break fn += 1
if fn>200:
def __del__(self): break
if self.dvd:
self.lib.DVDClose(self.dvd) def __del__(self):
if self.dvd:
def open(self, path): self.lib.DVDClose(self.dvd)
# self.dvd_css=self.css_lib.dvdcss_open()
self.dvd = self.lib.DVDOpen(bytes(path, "utf8")) def open(self, path):
vol_id = self.ffi.new("unsigned char[]", 32) # self.dvd_css=self.css_lib.dvdcss_open()
self.lib.DVDDiscID(self.dvd, vol_id) self.dvd = self.lib.DVDOpen(bytes(path, "utf8"))
self.disc_id = str(binascii.hexlify(self.ffi.buffer(vol_id, 16)[:]), "utf8") vol_id = self.ffi.new("unsigned char[]", 32)
self.lib.DVDUDFVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0) self.lib.DVDDiscID(self.dvd, vol_id)
self.udf_disc_name = str(self.ffi.string(vol_id), "utf8") self.disc_id = str(binascii.hexlify(self.ffi.buffer(vol_id, 16)[:]), "utf8")
self.lib.DVDISOVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0) self.lib.DVDUDFVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0)
self.iso_disc_name = str(self.ffi.string(vol_id), "utf8") self.udf_disc_name = str(self.ffi.string(vol_id), "utf8")
self.ffi.release(vol_id) 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 itertools as ITT
import json import json
import os import os
import subprocess as SP import subprocess as SP
import itertools as ITT import sys
from tqdm import tqdm from pprint import pprint
from fractions import Fraction
from glob import glob
colorspace = { from collections import Counter
"gbr": 0, from tqdm import tqdm
"bt709": 1, from time import perf_counter
"unknown": 2,
"fcc": 4,
"bt470bg": 5, def pulldown(fields_per_second, frames_per_second):
"smpte170m": 6, f = Fraction(fields_per_second, frames_per_second)
"smpte240m": 7,
"ycgco": 8,
"bt2020nc": 9, colorspace = {
"bt2020c": 10, "gbr": 0,
"smpte2085": 11, "bt709": 1,
"chroma-derived-nc": 12, "unknown": 2,
"chroma-derived-c": 13, "fcc": 4,
"ictcp": 14, "bt470bg": 5,
} "smpte170m": 6,
"smpte240m": 7,
pict_types = {"I": 0b01, "P": 0b10, "B": 0b11} "ycgco": 8,
"bt2020nc": 9,
"bt2020c": 10,
def make_info(frames): "smpte2085": 11,
has_interlaced = any(frame["interlaced_frame"] for frame in frames) "chroma-derived-nc": 12,
new_gop = "timecode" in frames[0].get("tags", {}) "chroma-derived-c": 13,
info = 0x000 "ictcp": 14,
info |= 1 << 11 # always 1 }
info |= 0 << 10 # 0=Closed GOP, 1=Open GOP
info |= (not has_interlaced) << 9 # Progressive pict_types = {"I": 0b01, "P": 0b10, "B": 0b11}
info |= new_gop << 8
return info
def make_info(frames):
has_interlaced = any(frame["interlaced_frame"] for frame in frames)
def make_flags(frames): new_gop = "timecode" in frames[0].get("tags", {})
flags = [] info = 0x000
for frame in frames: info |= 1 << 11 # always 1
needs_prev = False info |= 0 << 10 # 0=Closed GOP, 1=Open GOP
progressive = not int(frame["interlaced_frame"]) info |= (not has_interlaced) << 9 # Progressive
pt = pict_types[frame["pict_type"]] info |= new_gop << 8
reserved = 0b00 return info
tff = int(frame["top_field_first"])
rff = int(frame["repeat_pict"])
flag = 0b0 def make_flags(frames):
flag |= (not needs_prev) << 7 flags = []
flag |= progressive << 6 for frame in frames:
flag |= pt << 4 needs_prev = False
flag |= reserved << 2 progressive = not int(frame["interlaced_frame"])
flag |= tff << 1 pt = pict_types[frame["pict_type"]]
flag |= rff reserved = 0b00
flags.append(f"{flag:02x}") tff = int(frame["top_field_first"])
return flags rff = int(frame["repeat_pict"])
flag = 0b0
flag |= (not needs_prev) << 7
def make_line(frames, stream): flag |= progressive << 6
info = f"{make_info(frames):03x}" flag |= pt << 4
matrix = colorspace[stream["color_space"]] flag |= reserved << 2
file = 0 flag |= tff << 1
position = frames[0]["pkt_pos"] flag |= rff
skip = 0 flags.append(f"{flag:02x}")
vob = 0 return flags
cell = 0
flags = make_flags(frames)
return " ".join(map(str, [info, matrix, file, position, skip, vob, cell, *flags])) def make_line(frames, stream):
info = f"{make_info(frames):03x}"
matrix = colorspace[stream.get("color_space", "unknown")]
def get_frames(path): file = 0
proc = SP.Popen( position = frames[0]["pkt_pos"]
[ skip = 0
"ffprobe", vob = 0
"-probesize", cell = 0
str(0x7FFFFFFF), flags = make_flags(frames)
"-analyzeduration", return " ".join(map(str, [info, matrix, file, position, skip, vob, cell, *flags]))
str(0x7FFFFFFF),
"-v",
"fatal", def __make_dict(line):
"-i", ret = {}
path, line = line.strip().split("|")
"-select_streams", line_type = line[0]
"v:0", for value in line[1:]:
"-show_frames", entry = ret
"-print_format", if "=" not in value:
"compact", continue
], key_path, value = value.split("=")
stdout=SP.PIPE, key_path = key_path.split(".")
stdin=SP.DEVNULL, for key in key_path[:-1]:
bufsize=0, if ":" in key:
) key = key.split(":")[1]
data = None entry = entry.setdefault(key, {})
for line in proc.stdout: entry[key_path[-1]] = value
line = str(line, "utf8").strip().split("|") return {line_type: ret}
line = {line[0]: dict(v.split("=") for v in line[1:])}
yield line
ret = proc.wait() def judge(info, num_frames):
if ret != 0: threshold = 1 # BFF/TFF threshold value
exit(ret) min_num_frames = 250 # minimal number of frames
return data idet = info["frame"]["lavfi"]["idet"]
idet_v = {}
for t in "repeated", "single", "multiple":
def get_streams(path): idet_v[t] = {}
proc = SP.Popen( for k, v in idet[t].items():
[ try:
"ffprobe", idet_v[t][k] = int(v)
"-probesize", except ValueError:
str(0x7FFFFFFF), try:
"-analyzeduration", idet_v[t][k] = float(v)
str(0x7FFFFFFF), except ValueError:
"-v", pass
"fatal", idet = {
"-i", "repeat": {k: v for k, v in idet_v["repeated"].items()},
path, "single": {k: v for k, v in idet_v["single"].items()},
"-select_streams", "multiple": {k: v for k, v in idet_v["multiple"].items()},
"v:0", }
"-show_streams", repeat_err = abs(
"-show_format", (idet["repeat"]["neither"] / num_frames) - 0.8
"-print_format", ) # 2:3 pulldown,4 frames @ ~24 FPS to ~30 FPS = 20% repeated fields
"json", print(f"Derivation from 2:3 Pulldown: {repeat_err:.2%}")
], tff = idet["multiple"]["tff"]
stdout=SP.PIPE, bff = idet["multiple"]["bff"]
stdin=SP.DEVNULL, progressive = idet["multiple"]["progressive"]
bufsize=0, interlaced = tff + bff
) determined = interlaced + progressive
data = json.load(proc.stdout) print(f"Determined: {determined}")
ret = proc.wait() if interlaced:
if ret != 0: print(f"Interlaced: {interlaced} (TFF: {tff/interlaced:.2%}, BFF: {bff/interlaced:.2%}) = {interlaced/determined:.2%}")
exit(ret) else:
return data["streams"], data["format"] print(f"Interlaced: {interlaced} = {interlaced/determined:.2%}")
print(f"Progressive: {progressive} = {progressive/determined:.2%}")
if determined == 0:
def make_header(file): return idet
return ["DGIndexProjectFile16", "1", os.path.abspath(file)] idet["num_frames"] = num_frames
idet["interlaced"] = interlaced
idet["progressive"] = progressive
def make_settings(stream): if determined < 50 or determined < min_num_frames:
pict_size = "x".join(map(str, [stream["width"], stream["height"]])) print("/!\\ Not enough information to determine interlacing type reliably, results may be inacurate /!\\")
frame_rate = list(map(int, stream["r_frame_rate"].split("/"))) if interlaced > progressive:
frame_rate = (frame_rate[0] * 1000) // frame_rate[1] if tff > bff:
frame_rate = f"{frame_rate} ({stream['r_frame_rate']})" if repeat_err < 1.0:
header = [ idet["vid_type"] = "Telecined TFF"
("Stream_Type", 0), # Elementary Stream else:
("MPEG_Type", 2), # MPEG-2 idet["vid_type"] = "Interlaced TFF"
("iDCT_Algorithm", 5), # 64-bit IEEE-1180 Reference elif bff > tff:
("YUVRGB_Scale", int(stream["color_range"] != "tv")), if repeat_err < 1.0:
("Luminance_Filter", "0,0"), idet["vid_type"] = "Telecined BFF"
("Clipping", "0,0,0,0"), else:
("Aspect_Ratio", stream["display_aspect_ratio"]), idet["vid_type"] = "Interlaced BFF"
("Picture_Size", pict_size), else:
("Field_Operation", 0), # Honor Pulldown Flags idet["vid_type"] = "Interlaced?"
("Frame_Rate", frame_rate), else:
("Location", "0,0,0,0"), idet["vid_type"] = "Progressive"
] print(f"Result: {idet['vid_type']}")
for k, v in header: return idet
yield f"{k}={v}"
def get_meta_interlacing(path):
def gen_d2v(path): path = path.replace("\\", "/")
yield from make_header(path) filtergraph = [
yield "" f"movie=\\'{path}\\'",
streams, fmt = get_streams(path) "cropdetect=limit=0.5:round=2",
stream = [s for s in streams if s["codec_type"] == "video"][0] "idet",
stream["index"] = str(stream["index"]) ]
yield from make_settings(stream) proc = SP.Popen(
yield "" [
line_buffer = [] "ffprobe",
frames = get_frames(path) "-loglevel",
prog_bar = tqdm( "fatal",
frames, "-probesize",
total=int(fmt["size"]), str(0x7FFFFFFF),
unit_divisor=1024, "-analyzeduration",
unit_scale=True, str(0x7FFFFFFF),
unit="iB", "-f",
desc="Writing d2v", "lavfi",
) "-i",
for line in prog_bar: ",".join(filtergraph),
if "frame" not in line: "-select_streams",
continue "v",
frame = line["frame"] "-show_frames",
prog_bar.n = min(max(prog_bar.n, int(frame["pkt_pos"])), int(fmt["size"])) "-show_streams",
prog_bar.update(0) "-print_format",
if frame["stream_index"] != stream["index"]: "compact",
continue ],
if frame["pict_type"] == "I" and line_buffer: stdout=SP.PIPE,
yield make_line(line_buffer, stream) stdin=SP.DEVNULL,
line_buffer.clear() bufsize=0,
line_buffer.append(frame) encoding="utf8",
prog_bar.close() )
yield None total_size = int(get_streams(path)[1]["size"])
data = {}
pbar = tqdm(
def make_d2v(path): total=total_size,
outfile = os.path.splitext(os.path.basename(path))[0] desc="Analyzing video",
outfile = os.path.extsep.join([outfile, "d2v"]) unit_divisor=1024,
a, b = ITT.tee(gen_d2v(path)) unit_scale=True,
next(b) unit="iB",
with open(outfile, "w") as fh: leave=False,
for line, next_line in zip(a, b): )
fh.write(line) frame_num = 0
if next_line is None: # last line, append end marker from pprint import pformat, pprint
fh.write(" ff")
fh.write("\n") 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>, * Copyright (C) 2000, 2001, 2002 Björn Englund <d4bjorn@dtek.chalmers.se>,
* Håkan Hjort <d95hjort@dtek.chalmers.se> * Håkan Hjort <d95hjort@dtek.chalmers.se>
* *
* This file is part of libdvdread. * This file is part of libdvdread.
* *
* libdvdread is free software; you can redistribute it and/or modify * libdvdread is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or * the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* libdvdread is distributed in the hope that it will be useful, * libdvdread is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License along * You should have received a copy of the GNU General Public License along
* with libdvdread; if not, write to the Free Software Foundation, Inc., * with libdvdread; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
/** /**
* handle = ifoOpen(dvd, title); * handle = ifoOpen(dvd, title);
* *
* Opens an IFO and reads in all the data for the IFO file corresponding to the * 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. * given title. If title 0 is given, the video manager IFO file is read.
* Returns a handle to a completely parsed structure. * Returns a handle to a completely parsed structure.
*/ */
ifo_handle_t *ifoOpen(dvd_reader_t *, int ); ifo_handle_t *ifoOpen(dvd_reader_t *, int );
/** /**
* handle = ifoOpenVMGI(dvd); * handle = ifoOpenVMGI(dvd);
* *
* Opens an IFO and reads in _only_ the vmgi_mat data. This call can be used * 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 * together with the calls below to read in each segment of the IFO file on
* demand. * demand.
*/ */
ifo_handle_t *ifoOpenVMGI(dvd_reader_t *); ifo_handle_t *ifoOpenVMGI(dvd_reader_t *);
/** /**
* handle = ifoOpenVTSI(dvd, title); * handle = ifoOpenVTSI(dvd, title);
* *
* Opens an IFO and reads in _only_ the vtsi_mat data. This call can be used * 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 * together with the calls below to read in each segment of the IFO file on
* demand. * demand.
*/ */
ifo_handle_t *ifoOpenVTSI(dvd_reader_t *, int); ifo_handle_t *ifoOpenVTSI(dvd_reader_t *, int);
/** /**
* ifoClose(ifofile); * ifoClose(ifofile);
* Cleans up the IFO information. This will free all data allocated for the * Cleans up the IFO information. This will free all data allocated for the
* substructures. * substructures.
*/ */
void ifoClose(ifo_handle_t *); void ifoClose(ifo_handle_t *);
/** /**
* The following functions are for reading only part of the VMGI/VTSI files. * 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. * Returns 1 if the data was successfully read and 0 on error.
*/ */
/** /**
* okay = ifoRead_PLT_MAIT(ifofile); * okay = ifoRead_PLT_MAIT(ifofile);
* *
* Read in the Parental Management Information table, filling the * Read in the Parental Management Information table, filling the
* ifofile->ptl_mait structure and its substructures. This data is only * ifofile->ptl_mait structure and its substructures. This data is only
* located in the video manager information file. This fills the * located in the video manager information file. This fills the
* ifofile->ptl_mait structure and all its substructures. * ifofile->ptl_mait structure and all its substructures.
*/ */
int ifoRead_PTL_MAIT(ifo_handle_t *); int ifoRead_PTL_MAIT(ifo_handle_t *);
/** /**
* okay = ifoRead_VTS_ATRT(ifofile); * okay = ifoRead_VTS_ATRT(ifofile);
* *
* Read in the attribute table for the main menu vob, filling the * Read in the attribute table for the main menu vob, filling the
* ifofile->vts_atrt structure and its substructures. Only located in the * ifofile->vts_atrt structure and its substructures. Only located in the
* video manager information file. This fills in the ifofile->vts_atrt * video manager information file. This fills in the ifofile->vts_atrt
* structure and all its substructures. * structure and all its substructures.
*/ */
int ifoRead_VTS_ATRT(ifo_handle_t *); int ifoRead_VTS_ATRT(ifo_handle_t *);
/** /**
* okay = ifoRead_TT_SRPT(ifofile); * okay = ifoRead_TT_SRPT(ifofile);
* *
* Reads the title info for the main menu, filling the ifofile->tt_srpt * 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 * structure and its substructures. This data is only located in the video
* manager information file. This structure is mandatory in the IFO file. * manager information file. This structure is mandatory in the IFO file.
*/ */
int ifoRead_TT_SRPT(ifo_handle_t *); int ifoRead_TT_SRPT(ifo_handle_t *);
/** /**
* okay = ifoRead_VTS_PTT_SRPT(ifofile); * okay = ifoRead_VTS_PTT_SRPT(ifofile);
* *
* Reads in the part of title search pointer table, filling the * Reads in the part of title search pointer table, filling the
* ifofile->vts_ptt_srpt structure and its substructures. This data is only * ifofile->vts_ptt_srpt structure and its substructures. This data is only
* located in the video title set information file. This structure is * located in the video title set information file. This structure is
* mandatory, and must be included in the VTSI file. * mandatory, and must be included in the VTSI file.
*/ */
int ifoRead_VTS_PTT_SRPT(ifo_handle_t *); int ifoRead_VTS_PTT_SRPT(ifo_handle_t *);
/** /**
* okay = ifoRead_FP_PGC(ifofile); * okay = ifoRead_FP_PGC(ifofile);
* *
* Reads in the first play program chain data, filling the * Reads in the first play program chain data, filling the
* ifofile->first_play_pgc structure. This data is only located in the video * ifofile->first_play_pgc structure. This data is only located in the video
* manager information file (VMGI). This structure is optional. * manager information file (VMGI). This structure is optional.
*/ */
int ifoRead_FP_PGC(ifo_handle_t *); int ifoRead_FP_PGC(ifo_handle_t *);
/** /**
* okay = ifoRead_PGCIT(ifofile); * okay = ifoRead_PGCIT(ifofile);
* *
* Reads in the program chain information table for the video title set. Fills * Reads in the program chain information table for the video title set. Fills
* in the ifofile->vts_pgcit structure and its substructures, which includes * 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 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 * the video title set information file. This structure is mandatory, and must
* be included in the VTSI file. * be included in the VTSI file.
*/ */
int ifoRead_PGCIT(ifo_handle_t *); int ifoRead_PGCIT(ifo_handle_t *);
/** /**
* okay = ifoRead_PGCI_UT(ifofile); * okay = ifoRead_PGCI_UT(ifofile);
* *
* Reads in the menu PGCI unit table for the menu VOB. For the video manager, * 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 * 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 * 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 * video manager and video title set information files. For VMGI files, this
* fills the ifofile->vmgi_pgci_ut structure and all its substructures. For * fills the ifofile->vmgi_pgci_ut structure and all its substructures. For
* VTSI files, this fills the ifofile->vtsm_pgci_ut structure. * VTSI files, this fills the ifofile->vtsm_pgci_ut structure.
*/ */
int ifoRead_PGCI_UT(ifo_handle_t *); int ifoRead_PGCI_UT(ifo_handle_t *);
/** /**
* okay = ifoRead_VTS_TMAPT(ifofile); * okay = ifoRead_VTS_TMAPT(ifofile);
* *
* Reads in the VTS Time Map Table, this data is only located in the video * 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 * title set information file. This fills the ifofile->vts_tmapt structure
* and all its substructures. When pressent enables VOBU level time-based * and all its substructures. When pressent enables VOBU level time-based
* seeking for One_Sequential_PGC_Titles. * seeking for One_Sequential_PGC_Titles.
*/ */
int ifoRead_VTS_TMAPT(ifo_handle_t *); int ifoRead_VTS_TMAPT(ifo_handle_t *);
/** /**
* okay = ifoRead_C_ADT(ifofile); * okay = ifoRead_C_ADT(ifofile);
* *
* Reads in the cell address table for the menu VOB. For the video manager, * 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 * 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 * 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 * 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 * fills the ifofile->vmgm_c_adt structure and all its substructures. For VTSI
* files, this fills the ifofile->vtsm_c_adt structure. * files, this fills the ifofile->vtsm_c_adt structure.
*/ */
int ifoRead_C_ADT(ifo_handle_t *); int ifoRead_C_ADT(ifo_handle_t *);
/** /**
* okay = ifoRead_TITLE_C_ADT(ifofile); * okay = ifoRead_TITLE_C_ADT(ifofile);
* *
* Reads in the cell address table for the video title set corresponding to * 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 * 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. * 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. * This call fills the ifofile->vts_c_adt structure and its substructures.
*/ */
int ifoRead_TITLE_C_ADT(ifo_handle_t *); int ifoRead_TITLE_C_ADT(ifo_handle_t *);
/** /**
* okay = ifoRead_VOBU_ADMAP(ifofile); * okay = ifoRead_VOBU_ADMAP(ifofile);
* *
* Reads in the VOBU address map for the menu VOB. For the video manager, this * 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 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 * 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 * video manager and video title set information files. For VMGI files, this
* fills the ifofile->vmgm_vobu_admap structure and all its substructures. For * fills the ifofile->vmgm_vobu_admap structure and all its substructures. For
* VTSI files, this fills the ifofile->vtsm_vobu_admap structure. * VTSI files, this fills the ifofile->vtsm_vobu_admap structure.
*/ */
int ifoRead_VOBU_ADMAP(ifo_handle_t *); int ifoRead_VOBU_ADMAP(ifo_handle_t *);
/** /**
* okay = ifoRead_TITLE_VOBU_ADMAP(ifofile); * okay = ifoRead_TITLE_VOBU_ADMAP(ifofile);
* *
* Reads in the VOBU address map for the associated video title set. This data * 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 * is only located in the video title set information file. This structure is
* mandatory, and must be included in the VTSI file. Fills the * mandatory, and must be included in the VTSI file. Fills the
* ifofile->vts_vobu_admap structure and its substructures. * ifofile->vts_vobu_admap structure and its substructures.
*/ */
int ifoRead_TITLE_VOBU_ADMAP(ifo_handle_t *); int ifoRead_TITLE_VOBU_ADMAP(ifo_handle_t *);
/** /**
* okay = ifoRead_TXTDT_MGI(ifofile); * okay = ifoRead_TXTDT_MGI(ifofile);
* *
* Reads in the text data strings for the DVD. Fills the ifofile->txtdt_mgi * 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 * structure and all its substructures. This data is only located in the video
* manager information file. This structure is mandatory, and must be included * manager information file. This structure is mandatory, and must be included
* in the VMGI file. * in the VMGI file.
*/ */
int ifoRead_TXTDT_MGI(ifo_handle_t *); int ifoRead_TXTDT_MGI(ifo_handle_t *);
/** /**
* The following functions are used for freeing parsed sections of the * The following functions are used for freeing parsed sections of the
* ifo_handle_t structure and the allocated substructures. The free calls * 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 * 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. * file which was not read in or which does not exist.
*/ */
void ifoFree_PTL_MAIT(ifo_handle_t *); void ifoFree_PTL_MAIT(ifo_handle_t *);
void ifoFree_VTS_ATRT(ifo_handle_t *); void ifoFree_VTS_ATRT(ifo_handle_t *);
void ifoFree_TT_SRPT(ifo_handle_t *); void ifoFree_TT_SRPT(ifo_handle_t *);
void ifoFree_VTS_PTT_SRPT(ifo_handle_t *); void ifoFree_VTS_PTT_SRPT(ifo_handle_t *);
void ifoFree_FP_PGC(ifo_handle_t *); void ifoFree_FP_PGC(ifo_handle_t *);
void ifoFree_PGCIT(ifo_handle_t *); void ifoFree_PGCIT(ifo_handle_t *);
void ifoFree_PGCI_UT(ifo_handle_t *); void ifoFree_PGCI_UT(ifo_handle_t *);
void ifoFree_VTS_TMAPT(ifo_handle_t *); void ifoFree_VTS_TMAPT(ifo_handle_t *);
void ifoFree_C_ADT(ifo_handle_t *); void ifoFree_C_ADT(ifo_handle_t *);
void ifoFree_TITLE_C_ADT(ifo_handle_t *); void ifoFree_TITLE_C_ADT(ifo_handle_t *);
void ifoFree_VOBU_ADMAP(ifo_handle_t *); void ifoFree_VOBU_ADMAP(ifo_handle_t *);
void ifoFree_TITLE_VOBU_ADMAP(ifo_handle_t *); void ifoFree_TITLE_VOBU_ADMAP(ifo_handle_t *);
void ifoFree_TXTDT_MGI(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 json
import os import os
import json import subprocess as SP
import subprocess as SP import sys
import shutil
def get_streams(path):
proc = SP.Popen( def get_streams(path):
[ proc = SP.Popen(
"ffprobe", [
"-probesize", "ffprobe",
str(0x7FFFFFFF), "-probesize",
"-analyzeduration", str(0x7FFFFFFF),
str(0x7FFFFFFF), "-analyzeduration",
"-v", str(0x7FFFFFFF),
"fatal", "-v",
"-i", "fatal",
path, "-i",
"-show_streams", path,
"-show_format", "-show_streams",
"-print_format", "-show_format",
"json", "-print_format",
], "json",
stdout=SP.PIPE, ],
stdin=SP.DEVNULL, stdout=SP.PIPE,
bufsize=0, stdin=SP.DEVNULL,
) bufsize=0,
data = json.load(proc.stdout) )
ret = proc.wait() data = json.load(proc.stdout)
if ret != 0: ret = proc.wait()
return [], {} if ret != 0:
return data["streams"], data["format"] return [], {}
return data["streams"], data["format"]
types = { def ccextract(files):
"mpeg2video": "m2v", ccextractor = shutil.which("ccextractor") or shutil.which("ccextractorwinfull")
"ac3": "ac3", if ccextractor is None and os.name=="nt":
"dvd_subtitle": "sup", ccextractor=os.path.expandvars(os.path.join("${PROGRAMFILES(X86)}","CCExtractor","ccextractorwinfull.exe"))
} if not os.path.isfile(ccextractor):
print("WARNING: CCExtractor not found")
return []
def demux(path): new_files=[]
folder = os.path.dirname(path) for file in files:
basename = os.path.splitext(os.path.basename(path))[0] outfile=os.path.splitext(file)[0]
streams, fmt = get_streams(path) outfile=os.path.extsep.join([outfile, "cc.srt"])
cmd = [ ret=SP.call([ccextractor, "-sc", "-sbs", "-autodash", "-trim","-nobom","-o", outfile, file])
"ffmpeg", if ret==10:
"-y", if os.path.isfile(outfile):
# "-fflags","+genpts+igndts", os.unlink(outfile)
"-probesize", continue
str(0x7FFFFFFF), new_files.append(outfile)
"-analyzeduration", return new_files
str(0x7FFFFFFF),
"-i",
path, types = {"mpeg2video": "m2v", "ac3": "ac3", "dvd_subtitle": "sub.mkv", "eia_608": "srt"}
"-strict",
"-2", def demux(path):
"-vcodec", folder = os.path.dirname(path)
"copy", basename = os.path.splitext(os.path.basename(path))[0]
"-acodec", streams, _ = get_streams(path)
"copy", cmd = [
"-scodec", "ffmpeg",
"copy", "-y",
] "-probesize",
need_ffmpeg = False str(0x7FFFFFFF),
for stream in streams: "-analyzeduration",
codec = stream["codec_name"] str(0x7FFFFFFF),
ext = types.get(codec, codec) "-i",
idx = stream["index"] path,
hex_id = stream["id"] "-strict",
codec_name = stream["codec_long_name"] "-2",
outfile = os.path.join(folder, f"{basename}_{idx}_{hex_id}") ]
if codec=="dvd_nav_packet": caption_files = []
continue for stream in streams:
print(idx, hex_id, codec_name, codec) codec = stream["codec_name"]
if codec == "dvd_subtitle": ext = types.get(codec, codec)
SP.check_call([ hex_id = stream["id"]
"mencoder",path,"-vobsuboutindex",str(idx),"-vobsubout", outfile,"-nosound","-ovc", "copy", "-o",os.devnull codec_name = stream["codec_long_name"]
]) outfile = os.path.join(folder, f"{basename}_{hex_id}")
continue if codec == "dvd_nav_packet":
cmd += ["-map", f"0:#{hex_id}", "-strict", "-2", outfile + f".{ext}"] continue
need_ffmpeg = True outfile = os.path.extsep.join([outfile, ext])
if need_ffmpeg: print(hex_id, codec_name, codec)
SP.check_call(cmd) if codec == "mpeg2video":
caption_files.append(outfile)
if __name__=="__main__": cmd += [
demux(sys.argv[1]) "-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])