set_cd_marker.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. #!/usr/bin/env python3
  2. """
  3. Copyright © 2024 Noah Vogt <noah@noahvogt.com>
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. """
  15. from os import path, mkdir
  16. from shlex import split
  17. from subprocess import Popen
  18. from utils import (
  19. get_yyyy_mm_dd_date,
  20. make_sure_file_exists,
  21. is_valid_cd_record_checkfile,
  22. get_unix_milis,
  23. log,
  24. warn,
  25. error_msg,
  26. expand_dir,
  27. )
  28. from input import get_cachefile_content, validate_cd_record_config
  29. import config as const
  30. def start_cd_recording() -> int:
  31. date = get_yyyy_mm_dd_date()
  32. filename = path.join(
  33. const.CD_RECORD_OUTPUT_BASEDIR,
  34. date,
  35. "{}.wav".format(date),
  36. )
  37. log("starting cd recording...")
  38. cmd = "ffmpeg -y {} -ar 44100 -t {} {}".format(
  39. const.CD_RECORD_FFMPEG_INPUT_ARGS,
  40. const.CD_RECORD_MAX_SECONDS,
  41. filename,
  42. )
  43. process = Popen(split(cmd))
  44. return process.pid
  45. def create_cachefile_for_marker(
  46. cachefile_content: list,
  47. yyyy_mm_dd: str,
  48. unix_milis: int,
  49. *ffmpeg_recording_pid: int,
  50. initial_run=False,
  51. ) -> None:
  52. cachefile = expand_dir(const.CD_RECORD_CACHEFILE)
  53. if initial_run:
  54. marker = 1
  55. else:
  56. marker = int(cachefile_content[1]) + 1
  57. if marker > 99:
  58. return
  59. if (
  60. not (initial_run)
  61. and unix_milis - int(cachefile_content[4])
  62. < const.CD_RECORD_MIN_TRACK_MILIS
  63. ):
  64. return
  65. log("writing cd marker {} to cachefile...".format(marker))
  66. try:
  67. with open(cachefile, mode="w+", encoding="utf-8-sig") as file_writer:
  68. file_writer.write(yyyy_mm_dd + "\n")
  69. file_writer.write(str(marker) + "\n")
  70. if initial_run:
  71. file_writer.write("{}\n".format(ffmpeg_recording_pid[0]))
  72. file_writer.write(str(unix_milis) + "\n")
  73. else:
  74. file_writer.write(cachefile_content[2].strip() + "\n")
  75. file_writer.write(cachefile_content[3].strip() + "\n")
  76. file_writer.write(str(unix_milis) + "\n")
  77. except (FileNotFoundError, PermissionError, IOError) as error:
  78. error_msg(
  79. "Failed to write to cachefile '{}'. Reason: {}".format(
  80. cachefile, error
  81. )
  82. )
  83. def update_cue_sheet(
  84. cachefile_content: list, yyyy_mm_dd: str, unix_milis: int, initial_run=False
  85. ) -> None:
  86. cue_sheet_dir = path.join(
  87. expand_dir(const.CD_RECORD_OUTPUT_BASEDIR), yyyy_mm_dd
  88. )
  89. cue_sheet_path = path.join(cue_sheet_dir, "sheet.cue")
  90. wave_path = path.join(cue_sheet_dir, "{}.wav".format(yyyy_mm_dd))
  91. if initial_run:
  92. log("updating cue sheet...")
  93. try:
  94. if not path.exists(cue_sheet_dir):
  95. mkdir(cue_sheet_dir)
  96. with open(
  97. cue_sheet_path, mode="w+", encoding="utf-8-sig"
  98. ) as file_writer:
  99. file_writer.write('FILE "{}" WAVE\n'.format(wave_path))
  100. file_writer.write(" TRACK 01 AUDIO\n")
  101. file_writer.write(" INDEX 01 00:00:00\n")
  102. except (FileNotFoundError, PermissionError, IOError) as error:
  103. error_msg(
  104. "Failed to write to cue sheet file '{}'. Reason: {}".format(
  105. cue_sheet_path, error
  106. )
  107. )
  108. else:
  109. marker = int(cachefile_content[1]) + 1
  110. if marker > 99:
  111. warn("An Audio CD can only hold up to 99 tracks.")
  112. return
  113. start_milis = int(cachefile_content[3])
  114. last_track_milis = int(cachefile_content[4])
  115. if unix_milis - last_track_milis < const.CD_RECORD_MIN_TRACK_MILIS:
  116. warn(
  117. "Minimum track length of {}ms not satisfied".format(
  118. const.CD_RECORD_MIN_TRACK_MILIS
  119. )
  120. )
  121. return
  122. milis_diff = unix_milis - start_milis
  123. mins = milis_diff // 60000
  124. milis_diff -= 60000 * mins
  125. secs = int(milis_diff / 1000)
  126. milis_diff -= 1000 * secs
  127. frames = int(75 / 1000 * milis_diff)
  128. log("updating cue sheet...")
  129. try:
  130. with open(
  131. cue_sheet_path, mode="a", encoding="utf-8-sig"
  132. ) as file_writer:
  133. file_writer.write(" TRACK {:02d} AUDIO\n".format(marker))
  134. file_writer.write(
  135. " INDEX 01 {:02d}:{:02d}:{:02d}\n".format(
  136. mins, secs, frames
  137. )
  138. )
  139. except (FileNotFoundError, PermissionError, IOError) as error:
  140. error_msg(
  141. "Failed to write to cue sheet file '{}'. Reason: {}".format(
  142. cue_sheet_path, error
  143. )
  144. )
  145. def set_cd_marker() -> None:
  146. cachefile_content = get_cachefile_content(const.CD_RECORD_CACHEFILE)
  147. yyyy_mm_dd = get_yyyy_mm_dd_date()
  148. unix_milis = get_unix_milis()
  149. cachefile_and_time_data = (cachefile_content, yyyy_mm_dd, unix_milis)
  150. if is_valid_cd_record_checkfile(*cachefile_and_time_data[:-1]):
  151. create_cachefile_for_marker(*cachefile_and_time_data)
  152. update_cue_sheet(*cachefile_and_time_data)
  153. else:
  154. pid = start_cd_recording()
  155. create_cachefile_for_marker(
  156. *cachefile_and_time_data, pid, initial_run=True
  157. )
  158. update_cue_sheet(*cachefile_and_time_data, initial_run=True)
  159. def main() -> None:
  160. validate_cd_record_config()
  161. make_sure_file_exists(const.CD_RECORD_CACHEFILE)
  162. set_cd_marker()
  163. if __name__ == "__main__":
  164. main()